Quick overview

Current status

Load the requiered libraries for the analysis:

#library(nCov2019)
library(leaflet)
library(dplyr)
library(ggplot2)
library(plotly)
library(scales)
library(xts)
library(dygraphs)
library(corrplot)
library(lubridate)
library(fmsb)
library(forecast)

Load the given data, and the one updated from 2019 Novel Coronavirus COVID-19 (2019-nCoV) Data Repository by Johns Hopkins CSSE

COVID<-read.csv("covid_19_data.csv")
COVID_2<-read.csv("COVID19_14-Apr.csv")

Format date:

Date<-as.Date(COVID_2$Date, format="%m/%d/%y") 

COVID_2$Date2<-Date

Filter to obtain the last date:

COVID_updated<-COVID_2 %>% filter(Date2==max(Date2))

World map cases:

leaflet(width = "100%") %>% 
  addProviderTiles("CartoDB.DarkMatter") %>% 
  setView(lng = 0, lat = 10, zoom = 1.5) %>% 
  addCircleMarkers(data = COVID_updated, 
                   lng = ~ Long,
                   lat = ~ Lat,
                   radius = ~ log(Confirmed+1),
                   color = rgb(218/255,65/255,56/255),
                   fillOpacity = ~ ifelse(Confirmed > 0, 1, 0),
                   stroke = FALSE,
                   label = ~ paste(Province.State,",",Country.Region, ": ", Confirmed)
                   )

Current top 10 countries by cases:

COVID_top<-COVID_2 %>% filter(Date2==max(Date2)) %>% 
  group_by(Country.Region) %>% summarise(Total_confirmed=sum(Confirmed)) %>% 
  top_n(10,Total_confirmed) %>% arrange(desc(Total_confirmed))
plot<-ggplot(data=COVID_top
       , aes(x=Total_confirmed,y=reorder(Country.Region,Total_confirmed))) +
  geom_bar(stat ="identity",alpha=0.8,fill="firebrick3") +
  geom_text(aes(label=Total_confirmed), vjust=0.5, hjust=0.9,color="black", size=3.5) +
  scale_x_continuous(labels = comma) +
  labs(title = paste("Top 10 countries with confirmed cases as of ",max(COVID_2$Date2)),
       x = "Confirmed cases",
       y = "Country") +
  theme_minimal()

ggplotly(plot,tooltip = c("x"),width=750)

Time distribution of cases, deaths and recovered:

COVID_2_Day<- COVID_2 %>% group_by(Date2) %>% summarise(World_confirmed=sum(Confirmed),
                                                        World_deaths=sum(Deaths),
                                                        World_recovered=sum(Recovered))


COVID_Day_confirmed_series<-xts(COVID_2_Day$World_confirmed, order.by=COVID_2_Day$Date2)
COVID_Day_deaths_series<-xts(COVID_2_Day$World_deaths, order.by=COVID_2_Day$Date2)
COVID_Day_recovered_series<-xts(COVID_2_Day$World_recovered, order.by=COVID_2_Day$Date2)

Day_summary<-cbind(COVID_Day_confirmed_series,COVID_Day_deaths_series,COVID_Day_recovered_series)
dygraph(Day_summary, main = "SARS-COV2-outbreak: Total worldwide cases", 
        xlab="Date", ylab="Total cases",width = 750) %>% 
  dySeries("COVID_Day_confirmed_series", "Total cases",drawPoints = TRUE, 
           pointSize = 3, color=rgb(53/255,116/255,199/255)) %>% 
  dySeries("COVID_Day_deaths_series", "Total deaths",drawPoints = TRUE, 
           pointSize = 3, color=rgb(189/255,55/255,48/255)) %>% 
  dySeries("COVID_Day_recovered_series", "Total recovered",drawPoints = TRUE, 
           pointSize = 3, color=rgb(69/255,136/255,51/255)) %>% 
  dyRangeSelector()

Create a function to calculate new cases:

New_count<-function(x)
{
  Daily_cases<-numeric(length(x))
  
  for(i in length(x):2)
  {
    Daily_cases[i]=x[i] - x[i-1]
  }
  return(Daily_cases)
}

New_cases<-New_count(COVID_2_Day$World_confirmed)
New_deaths<-New_count(COVID_2_Day$World_deaths)
New_recovered<-New_count(COVID_2_Day$World_recovered)
COVID_New_confirmed_series<-xts(New_cases, order.by=COVID_2_Day$Date2)
COVID_New_deaths_series<-xts(New_deaths, order.by=COVID_2_Day$Date2)
COVID_New_recovered_series<-xts(New_recovered, order.by=COVID_2_Day$Date2)

New_summary<-cbind(COVID_New_confirmed_series,COVID_New_deaths_series,COVID_New_recovered_series)

New cases, deaths and recovered time distribution:

dygraph(New_summary, main = "SARS-COV2-outbreak: Total worldwide cases", 
        xlab="Date", ylab="Novel coronavirus cases",width = 750) %>% 
  dySeries("COVID_New_confirmed_series", "New cases",drawPoints = TRUE, 
           pointSize = 3, color=rgb(53/255,116/255,199/255)) %>% 
  dySeries("COVID_New_deaths_series", "New deaths",drawPoints = TRUE, 
           pointSize = 3, color=rgb(189/255,55/255,48/255)) %>% 
  dySeries("COVID_New_recovered_series", "New recovered",drawPoints = TRUE, 
           pointSize = 3, color=rgb(69/255,136/255,51/255)) %>% 
  dyRangeSelector()

Total cases by some countries time distribution:

COVID_2_Day_Lebanon<- COVID_2 %>% 
  filter(Country.Region %in% c("Lebanon")) %>% 
  group_by(Date2) %>% summarise(World_confirmed=sum(Confirmed))

COVID_2_Day_Chile<- COVID_2 %>% 
  filter(Country.Region %in% c("Chile")) %>% 
  group_by(Date2) %>% summarise(World_confirmed=sum(Confirmed))

COVID_2_Day_Colombia<- COVID_2 %>% 
  filter(Country.Region %in% c("Colombia")) %>% 
  group_by(Date2) %>% summarise(World_confirmed=sum(Confirmed))

COVID_2_Day_CostaRica<- COVID_2 %>% 
  filter(Country.Region %in% c("Costa Rica")) %>% 
  group_by(Date2) %>% summarise(World_confirmed=sum(Confirmed))


COVID_Day_series_Lebanon<-xts(COVID_2_Day_Lebanon$World_confirmed, order.by=COVID_2_Day_Lebanon$Date2)
COVID_Day_series_Chile<-xts(COVID_2_Day_Chile$World_confirmed, order.by=COVID_2_Day_Chile$Date2)
COVID_Day_series_Colombia<-xts(COVID_2_Day_Colombia$World_confirmed, order.by=COVID_2_Day_Colombia$Date2)
COVID_Day_series_CostaRica<-xts(COVID_2_Day_CostaRica$World_confirmed, order.by=COVID_2_Day_CostaRica$Date2)

Our_Countries<-cbind(COVID_Day_series_Lebanon,COVID_Day_series_Chile,COVID_Day_series_Colombia,COVID_Day_series_CostaRica)
dygraph(Our_Countries, main = "SARS-COV2-outbreak: Total cases by country", xlab="Date", ylab="Total cases",width = 750) %>% 
  dySeries("COVID_Day_series_Lebanon", "Lebanon",drawPoints = TRUE, 
           pointSize = 3, color=rgb(0,0,3/255)) %>% 
  dySeries("COVID_Day_series_Chile", "Chile",drawPoints = TRUE, 
           pointSize = 3,color=rgb(120/255,28/255,109/255)) %>% 
  dySeries("COVID_Day_series_Colombia", "Colombia",drawPoints = TRUE, 
           pointSize = 3,color=rgb(237/255,105/255,37/255)) %>% 
  dySeries("COVID_Day_series_CostaRica", "Costa Rica",drawPoints = TRUE,
           pointSize = 3,color=rgb(204/255,197/255,126/255)) %>% 
  dyRangeSelector()

New cases by some countries time distribution:

New_Lebanon<-New_count(COVID_2_Day_Lebanon$World_confirmed)
New_Chile<-New_count(COVID_2_Day_Chile$World_confirmed)
New_Colombia<-New_count(COVID_2_Day_Colombia$World_confirmed)
New_CostaRica<-New_count(COVID_2_Day_CostaRica$World_confirmed)

COVID_New_series_Lebanon<-xts(New_Lebanon, order.by=COVID_2_Day_Lebanon$Date2)
COVID_New_series_Chile<-xts(New_Chile, order.by=COVID_2_Day_Chile$Date2)
COVID_New_series_Colombia<-xts(New_Colombia, order.by=COVID_2_Day_Colombia$Date2)
COVID_New_series_CostaRica<-xts(New_CostaRica, order.by=COVID_2_Day_CostaRica$Date2)

Our_New_Countries<-cbind(COVID_New_series_Lebanon,COVID_New_series_Chile,COVID_New_series_Colombia,COVID_New_series_CostaRica)
dygraph(Our_New_Countries, main = "SARS-COV2-outbreak: New cases by country", xlab="Date", ylab="Total cases",width = 750) %>% 
  dySeries("COVID_New_series_Lebanon", "Lebanon",drawPoints = TRUE, 
           pointSize = 3, color=rgb(0,0,3/255)) %>% 
  dySeries("COVID_New_series_Chile", "Chile",drawPoints = TRUE, 
           pointSize = 3,color=rgb(120/255,28/255,109/255)) %>% 
  dySeries("COVID_New_series_Colombia", "Colombia",drawPoints = TRUE, 
           pointSize = 3,color=rgb(237/255,105/255,37/255)) %>% 
  dySeries("COVID_New_series_CostaRica", "Costa Rica",drawPoints = TRUE,
           pointSize = 3,color=rgb(204/255,197/255,126/255)) %>% 
  dyRangeSelector()

Looking for correlations

3D scatterplot of confirmed cases Vs. Deaths Vs. Recovered, using the data for the last day

fig <- plot_ly(COVID_updated, x = ~Confirmed, y = ~Deaths, z = ~Recovered, width=750) %>% 
  add_markers(text= ~Country.Region ,hoverinfo= "text",
              marker = list(color=rgb(189/255,55/255,48/255))) %>% 
  layout(title="Confirmed cases Vs. Deaths Vs. Recovered", scene = list(
                    xaxis = list(title = 'Confirmed'),
                     yaxis = list(title = 'Deaths'),
                     zaxis = list(title = 'Recovered'))) 
fig

For the number of cases

In this step, we will look for correlations of the number of cases of COVID-19, by some known indicators. Here each individual of analysis (tuple) is a country.

Human Development Index

The Human Development Index is calculated by the United Nations. And its located here: United Nations Human Development reports

HDI<-read.csv("Human Development Index (HDI)_2.csv",sep=";",dec=",")

Group data by country:

COVID_Country<-COVID_2 %>% filter(Date2==max(Date2)) %>% 
  group_by(Country.Region) %>% summarise(Total_confirmed=sum(Confirmed),
                                         Total_deaths=sum(Deaths),
                                         Total_Recovered=sum(Recovered))

Remove after parentheses: Use regular expresions to clean the countries name on the HDI file, this to use this variable as key to join with the COVID-19 countries table:

HDI$Country_2<-gsub("\\s*\\([^\\)]+\\)","",as.character(HDI$Country))
HDI$Country_2[HDI$Country_2=="United States"]<-"US"
HDI$Country_2[HDI$Country_2=="Korea"]<-"South Korea"

Because the number of cases by country is clearly affected by the population (for example the number of cases in Lebanon is way smaller than the US, but Lebanon has 6M inhabitants and the US 330M), this variable was used to obtain the number of cases per million inhabitants.

The data used is from the World Bank indicators

Population<-read.csv("World_population.csv",sep=";",dec=",")

Remove after commma: Use regular expresions to clean the countries name on the population file, this to use this variable as key to join with the COVID-19 countries table:

Population$Country_Name_2<-gsub(",.*", "", as.character(Population$Country_Name))
Population$Country_Name_2[Population$Country_Name_2=="United States"]<-"US"
Population$Country_Name_2[Population$Country_Code=="KOR"]<-"South Korea"
Population$Country_Name_2[Population$Country_Code=="CZE"]<-"Czechia"

Natural Join:

COVID_3<- COVID_Country %>% inner_join(HDI,by=c("Country.Region"="Country_2")) %>% 
  inner_join(Population,by=c("Country.Region"="Country_Name_2")) %>% 
  select(Country.Region,Total_confirmed,Total_deaths,Total_Recovered,HDI_Rank_2018,Year_2018,
         Country_Code,Population_2018) %>% 
  mutate(Cases_million=(Total_confirmed/Population_2018)*1000000,
         Recovered_percentage=(Total_Recovered/Total_confirmed)*100)  

COVID_3<-COVID_3[!is.na(COVID_3$Population_2018),]

Countries with top 10 cases per million inhabitants

COVID3_top<-COVID_3 %>% top_n(10,Cases_million) %>% arrange(desc(Cases_million)) %>% 
  mutate(Cases_million=round(Cases_million,0))
plot<-ggplot(data=COVID3_top
       , aes(x=Cases_million,y=reorder(Country.Region,Cases_million))) +
  geom_bar(stat ="identity",alpha=0.8,fill="sky blue", ) +
  geom_text(aes(label=Cases_million), vjust=0.5, hjust=0.9,color="black", size=3.5) +
  scale_x_continuous(labels = comma) +
  labs(title = paste("Top 10 countries with confirmed cases per million habitant \n as of ",max(COVID_2$Date2)),
       x = "Confirmed cases per million habitants",
       y = "Country") +
  theme_minimal()

ggplotly(plot,tooltip = c("x"),width=750)

Plot the Human Development Index(HDI) Vs. the number of cases (applying a log transformation, to avoid effects of outliers), and the proportion of recovered cases:

plot<-ggplot(data=COVID_3,aes(x=log(Cases_million),y=Year_2018,
                        size=Recovered_percentage,text=Country.Region)) +
  geom_point(color="black",fill=rgb(237/255,105/255,37/255),shape=21,alpha=0.6) +
  scale_size(range = c(3,15), name="Recovered \n percentage") +
  theme_minimal() + 
  theme(legend.position="bottom") +
  labs(title="HDI Vs. logarithmus of COVID-19 cases by million inhabitants \n and proportion of recovered",
       x="ln(Cases/1M population)",
       y="HDI")

ggplotly(plot,tooltip = c("text"),width=750)

Correlation matrix:

COVID_numeric_1<-COVID_3 %>% mutate(Log_cases=log(Cases_million),
                                    Death_percentage=(Total_deaths/Total_confirmed)*100) %>% 
  select(Log_cases,Recovered_percentage,Death_percentage,Year_2018)

corrplot(cor(COVID_numeric_1),method = "number",tl.col="black",tl.srt=15,
         col=colorRampPalette(c(rgb(204/255,197/255,126/255),rgb(237/255,105/255,37/255)))(200))

Measles<-read.csv("Measles_immunization.csv",sep=";",dec=".")
Measles$Country_2<-gsub("\\s*\\([^\\)]+\\)","",as.character(Measles$Country))
Measles$Country_2[Measles$Country_2=="United States"]<-"US"
Measles$Country_2[Measles$Country_2=="Korea"]<-"South Korea"
COVID_3<- COVID_3 %>% inner_join(Measles,by=c("Country.Region"="Country_2")) %>% select(-c("Country"))

Health expenditure (% of GDP)

The next variable to consider is the percentage of the GDP that the countries spend on health, which is also available on United Nations Human Development reports

Health_expenditure<-read.csv("Health_expenditure_GDP.csv",sep=";",dec=".")

Remove after parentheses: Use regular expresions to clean the countries name on the health expenditure file, this to use this variable as key to join with the COVID-19 countries table:

Health_expenditure$Country_2<-gsub("\\s*\\([^\\)]+\\)","",
                                   as.character(Health_expenditure$Country))
Health_expenditure$Country_2[Health_expenditure$Country_2=="United States"]<-"US"
Health_expenditure$Country_2[Health_expenditure$Country_2=="Korea"]<-"South Korea"

Natural join of tables:

COVID_3<- COVID_3 %>% inner_join(Health_expenditure,by=c("Country.Region"="Country_2")) %>% select(-c("Country"))

Plot the % of GDP spent on health Vs the Cases/1M population:

plot<-ggplot(data=COVID_3,aes(x=log(Cases_million),y=Expenditure_2016,
                        size=Recovered_percentage,text=Country.Region)) +
  geom_point(color="black",fill=rgb(204/255,197/255,126/255),shape=21,alpha=0.6) +
  scale_size(range = c(3,15), name="Recovered \n percentage") +
  theme_minimal() + 
  theme(legend.position="bottom") +
  labs(title="Health expenditure (% of GDP) Vs. logarithmus of COVID-19 cases by million inhabitants \n and proportion of recovered",
       x="ln(Cases/1M population)",
       y="% of GDP in health")

ggplotly(plot,tooltip = c("text"),width=750)

Temperature

Other variable to considered is the temperature of the countries. While the is no empirical evidence that the weather affects or helps the spread of the SARS-COV 2 virus. This paper covers how generally, the weather had affected the spread of viral respiratory infections

This data contains the temperature (in celcius) of the first day of the month, for all countries since 1900, and it is available on this Kaggle

Temperature<-read.csv("GlobalLandTemperaturesByCountry.csv",sep=";")

Format date:

Date_Temp<-as.Date(Temperature$dt, format="%d/%m/%Y") 

Temperature$Date_Temp<-Date_Temp

Extract month and filter for IQ (Jan, Feb and Mar), because that are the months our COVID data is available, and obtain the average IQ temperature:

Temperature<- Temperature %>% mutate(Month=month(Temperature$Date_Temp,label =TRUE),
                                     Year=year(Temperature$Date_Temp)) %>% 
  filter(Month %in% c("Jan","Feb","Mar") & Year>=2000)  %>% 
  group_by(Country) %>% summarise(Avg_IQ_temperature=mean(AverageTemperature))

Temperature<-Temperature[!is.na(Temperature$Avg_IQ_temperature),]

Regular expressions to clean the countries name:

Temperature$Country_2<-gsub("\\s*\\([^\\)]+\\)","",
                                   as.character(Temperature$Country))
Temperature$Country_2[Temperature$Country_2=="United States"]<-"US"
Temperature$Country_2[Temperature$Country_2=="Korea"]<-"South Korea"

Join tables:

COVID_3<- COVID_3 %>% inner_join(Temperature,by=c("Country.Region"="Country_2")) %>% select(-c("Country"))

#write.csv(COVID_3,"COVID_Covariables.csv")

Plot the average temperature of the first quarter Vs. the cases/1M habitants:

plot<-ggplot(data=COVID_3,aes(x=log(Cases_million),y=Avg_IQ_temperature,
                        size=Recovered_percentage,text=Country.Region)) +
  geom_point(color="black",fill=rgb(120/255,28/255,109/255),shape=21,alpha=0.6) +
  scale_size(range = c(3,15), name="Recovered \n percentage") +
  theme_minimal() + 
  theme(legend.position="bottom") +
  labs(title="Average IQ temperature Vs. logarithmus of COVID-19 cases by million inhabitants \n and proportion of recovered",
       x="ln(Cases/1M population)",
       y="Temperature (Celcius)")

ggplotly(plot,tooltip = c("text"),width=750)

For the number of deaths

This step is similar to the previous one, where each individual of analysis (tuple) is a country, but here we are considering the death rate percentage.

DTP immunization

The DTP is a class of combination vaccines against three infectious diseases in humans: diphtheria, pertussis (whooping cough), and tetanus, the data is also available on the United Nations Human Development reports

DTP_immunization<-read.csv("Infants_lacking_immunization_DTP.csv",sep=";")

Remove after parentheses: regular expression cleaning

DTP_immunization$Country_2<-gsub("\\s*\\([^\\)]+\\)","",
                                 as.character(DTP_immunization$Country))
DTP_immunization$Country_2[DTP_immunization$Country_2=="United States"]<-"US"
DTP_immunization$Country_2[DTP_immunization$Country_2=="Korea"]<-"South Korea"

Join tables:

COVID_4<- COVID_Country %>% inner_join(DTP_immunization,
                                       by=c("Country.Region"="Country_2")) %>% 
  select(Country.Region,Total_confirmed,Total_deaths,Total_Recovered,
         Lack_DTP_inmmunization_2018) %>% 
  mutate(Recovered_percentage=(Total_Recovered/Total_confirmed)*100,
         Death_rate=(Total_deaths/Total_confirmed)*100)

Plot the death rate Vs. % of infants lacking DTP immunization:

plot<-ggplot(data=COVID_4,aes(x=Death_rate,y=Lack_DTP_inmmunization_2018,
                        size=Recovered_percentage,text=Country.Region)) +
  geom_point(color="black",fill=rgb(120/255,28/255,109/255),shape=21,alpha=0.6) +
  scale_size(range = c(3,15), name="Recovered \n percentage") +
  theme_minimal() + 
  theme(legend.position="bottom") +
  labs(title="% infants lacking DTP immunization Vs. death rate and \n proportion of recovered",
       x="Novel coronavirus death rate",
       y="% of infants")

ggplotly(plot,tooltip = c("text"),width=750)

Infants lacking immunization, measles (% of one-year-olds)

The next vaccine that was evaluated was measles, the data is also available on the United Nations Human Development reports

COVID_4<- COVID_4 %>% inner_join(Measles,by=c("Country.Region"="Country_2")) %>% select(-c("Country"))

See the distribution of the % od infants lacking measles vaccine, the median of this variable is below 10%, with some outliers over 40% in some countries

ggplot(COVID_4, aes(y=Measles_2018)) + 
  geom_boxplot(fill="dodgerblue4",outlier.shape = 21, 
               outlier.fill = "firebrick",alpha=0.75) +
  ggtitle("Boxplot of % infants lacking measles immunization") + ylab("% of infants") +
  theme_minimal()

Histogram:

ggplot(COVID_4, aes(Measles_2018)) + 
  geom_histogram(fill="dodgerblue4",bins=20,alpha=0.8) +
  ggtitle("Histogram of % infants lacking measles immunization") + 
  xlab("% of infants") + 
  ylab("Count") +
  theme_minimal()

Plot % of infants lacking this immunization vs the fatality rate:

plot<-ggplot(data=COVID_4,aes(x=Death_rate,y=Measles_2018,
                        size=Recovered_percentage,text=Country.Region)) +
  geom_point(color="black",fill=rgb(237/255,105/255,37/255),shape=21,alpha=0.6) +
  scale_size(range = c(3,15), name="Recovered \n percentage") +
  theme_minimal() + 
  theme(legend.position="bottom") +
  labs(title="% infants lacking measles immunization Vs. fatality rate \n and proportion of recovered",
       x="Fatality rate (%)",
       y="% of infants")

ggplotly(plot,tooltip = c("text"),width=750)

Fitting a regression model

Here the individuals of analysis (tuples) will be each country, the response variable will be the cases per million inhabitants, and the explanatory variables showed before.

Because of the presence of extreme cases per million inhabitants in some countries, it is convenient to make a ln transformation, and because some temperatures are below 0 celcius, they will be converted to kelvin (adding 273.5) to the celcius temperature.

First model:

Mod1<-lm(log(COVID_3$Cases_million)~log(COVID_3$Year_2018)+log(COVID_3$Measles_2018)+
           log(COVID_3$Expenditure_2016)+log(COVID_3$Avg_IQ_temperature+273.15))
summary(Mod1)

Call:
lm(formula = log(COVID_3$Cases_million) ~ log(COVID_3$Year_2018) + 
    log(COVID_3$Measles_2018) + log(COVID_3$Expenditure_2016) + 
    log(COVID_3$Avg_IQ_temperature + 273.15))

Residuals:
    Min      1Q  Median      3Q     Max 
-4.1125 -0.8551 -0.1668  0.8243  5.1749 

Coefficients:
                                         Estimate Std. Error t value Pr(>|t|)    
(Intercept)                              35.85291   20.09897   1.784  0.07662 .  
log(COVID_3$Year_2018)                    7.62697    0.67683  11.269  < 2e-16 ***
log(COVID_3$Measles_2018)                 0.03507    0.12139   0.289  0.77312    
log(COVID_3$Expenditure_2016)             0.88946    0.31841   2.793  0.00595 ** 
log(COVID_3$Avg_IQ_temperature + 273.15) -5.42430    3.53904  -1.533  0.12761    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.391 on 140 degrees of freedom
Multiple R-squared:  0.694, Adjusted R-squared:  0.6853 
F-statistic: 79.39 on 4 and 140 DF,  p-value: < 2.2e-16

Stepwise with AIC criterion, to see which variables are better:

Mod2<-step(Mod1,direction = "both")
Start:  AIC=100.7
log(COVID_3$Cases_million) ~ log(COVID_3$Year_2018) + log(COVID_3$Measles_2018) + 
    log(COVID_3$Expenditure_2016) + log(COVID_3$Avg_IQ_temperature + 
    273.15)

                                           Df Sum of Sq    RSS     AIC
- log(COVID_3$Measles_2018)                 1     0.162 271.19  98.782
<none>                                                  271.03 100.695
- log(COVID_3$Avg_IQ_temperature + 273.15)  1     4.548 275.57 101.108
- log(COVID_3$Expenditure_2016)             1    15.106 286.13 106.560
- log(COVID_3$Year_2018)                    1   245.830 516.86 192.300

Step:  AIC=98.78
log(COVID_3$Cases_million) ~ log(COVID_3$Year_2018) + log(COVID_3$Expenditure_2016) + 
    log(COVID_3$Avg_IQ_temperature + 273.15)

                                           Df Sum of Sq    RSS     AIC
<none>                                                  271.19  98.782
- log(COVID_3$Avg_IQ_temperature + 273.15)  1     4.474 275.66  99.154
+ log(COVID_3$Measles_2018)                 1     0.162 271.03 100.695
- log(COVID_3$Expenditure_2016)             1    15.410 286.60 104.795
- log(COVID_3$Year_2018)                    1   301.006 572.19 205.048

Final model:

summary(Mod2)

Call:
lm(formula = log(COVID_3$Cases_million) ~ log(COVID_3$Year_2018) + 
    log(COVID_3$Expenditure_2016) + log(COVID_3$Avg_IQ_temperature + 
    273.15))

Residuals:
    Min      1Q  Median      3Q     Max 
-4.0920 -0.8525 -0.1514  0.8196  5.1706 

Coefficients:
                                         Estimate Std. Error t value Pr(>|t|)    
(Intercept)                               35.5877    20.0126   1.778  0.07752 .  
log(COVID_3$Year_2018)                     7.5391     0.6026  12.510  < 2e-16 ***
log(COVID_3$Expenditure_2016)              0.8960     0.3166   2.831  0.00533 ** 
log(COVID_3$Avg_IQ_temperature + 273.15)  -5.3731     3.5231  -1.525  0.12947    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.387 on 141 degrees of freedom
Multiple R-squared:  0.6938,    Adjusted R-squared:  0.6873 
F-statistic: 106.5 on 3 and 141 DF,  p-value: < 2.2e-16

According to the AIC criterion, a model with HDI, % spent on health and average temperature is better:

Normality of residuals:

Model_residuals<-data.frame(Residuals=Mod2$residuals)
ggplot(Model_residuals, aes(Residuals)) + 
  geom_histogram(fill="dodgerblue4",bins=18,alpha=0.8) +
  ggtitle("Histogram of model residuals") + 
  xlab("Residuals") + 
  ylab("Frequency") +
  theme_minimal()


shapiro.test(Mod2$residuals)

    Shapiro-Wilk normality test

data:  Mod2$residuals
W = 0.98266, p-value = 0.06388

By analysing the histogram, and using the Shapiro-Wilk test, there is not enough statistical evidence to reject the null hypothesis that the residuals of the model are following a normal distribution. Normal distribution is assumed.

Prediction power

Evaluate if a model with temperature Vs. a one without temperature.

Separate between training and testing:

set.seed(179819)
Sample <- sample(1:length(COVID_3$Cases_million),length(COVID_3$Cases_million)*0.20)
t.testing<- COVID_3[Sample,]
t.training<- COVID_3[-Sample,]

Transform the training and testing variables as before:

t.training<-t.training %>% mutate(Cases_million_log=log(Cases_million),HDI_log=log(Year_2018),
                      GDP_log=log(Expenditure_2016),
                      Temperature_log_kelvin=log(Avg_IQ_temperature+273.15)) 

t.training<-t.training[,14:17]

t.testing<-t.testing %>% mutate(Cases_million_log=log(Cases_million),HDI_log=log(Year_2018),
                      GDP_log=log(Expenditure_2016),
                      Temperature_log_kelvin=log(Avg_IQ_temperature+273.15)) 

t.testing<-t.testing[,14:17]

Fit the same model with training

Mod3<-lm(Cases_million_log~., data=t.training)
summary(Mod3)

Call:
lm(formula = Cases_million_log ~ ., data = t.training)

Residuals:
    Min      1Q  Median      3Q     Max 
-4.0745 -0.7898 -0.0155  0.8534  5.1967 

Coefficients:
                       Estimate Std. Error t value Pr(>|t|)    
(Intercept)             46.0107    23.3366   1.972   0.0511 .  
HDI_log                  7.5427     0.6704  11.250   <2e-16 ***
GDP_log                  0.8721     0.3560   2.450   0.0158 *  
Temperature_log_kelvin  -7.1996     4.1003  -1.756   0.0818 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.404 on 112 degrees of freedom
Multiple R-squared:  0.7062,    Adjusted R-squared:  0.6983 
F-statistic: 89.72 on 3 and 112 DF,  p-value: < 2.2e-16

Error functions:

# Residual Sum of Square (RSS)
RSS<-function(Pred,Actual) {
  ss<-sum((Actual-Pred)^2)
  return(ss)
}

# Residual Standard Error (RSE)
RSE<-function(Pred,Real,NumPred) {
  N<-length(Real)-NumPred-1  
  ss<-sqrt((1/N)*RSS(Pred,Real))
  return(ss)
}
# Mean Squared Error 
MSE <- function(Pred,Actual) {
  N<-length(Actual)
  ss<-(1/N)*RSS(Pred,Actual)
  return(ss)
}

# Relative error
RelativeError<-function(Pred,Actual) {
  ss<-sum(abs(Actual-Pred))/sum(abs(Actual))
  return(ss)
}

Prediction:

Pred<-predict(Mod3,t.testing)

Errors:

RSS_Mod3<-RSS(Pred,t.testing$Cases_million_log)
RSE_Mod3<-RSE(Pred,t.testing$Cases_million_log,dim(t.testing)[2]-1)
MSE_Mod3<-MSE(Pred,t.testing$Cases_million_log)
RelativeError_Mod3<-RelativeError(Pred,t.testing$Cases_million_log)

Mod3_Errors<-c(RSS_Mod3,RSE_Mod3,MSE_Mod3,RelativeError_Mod3)

Now, a model without temperature:

t.training <- t.training %>% select(-Temperature_log_kelvin)
t.testing <- t.testing %>% select(-Temperature_log_kelvin)
Mod4<-lm(Cases_million_log~., data=t.training)
summary(Mod4)

Call:
lm(formula = Cases_million_log ~ ., data = t.training)

Residuals:
    Min      1Q  Median      3Q     Max 
-3.9956 -0.7580 -0.0306  0.8623  5.2520 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   5.0545     0.7316   6.909 3.04e-10 ***
HDI_log       8.1269     0.5874  13.836  < 2e-16 ***
GDP_log       1.0734     0.3401   3.156  0.00205 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.416 on 113 degrees of freedom
Multiple R-squared:  0.6981,    Adjusted R-squared:  0.6927 
F-statistic: 130.6 on 2 and 113 DF,  p-value: < 2.2e-16

Prediction:

Pred<-predict(Mod4,t.testing)

Errors:

RSS_Mod4<-RSS(Pred,t.testing$Cases_million_log)
RSE_Mod4<-RSE(Pred,t.testing$Cases_million_log,dim(t.testing)[2]-1)
MSE_Mod4<-MSE(Pred,t.testing$Cases_million_log)
RelativeError_Mod4<-RelativeError(Pred,t.testing$Cases_million_log)

Mod4_Errors<-c(RSS_Mod4,RSE_Mod4,MSE_Mod4,RelativeError_Mod4)

Create a radarplot to easily compare errors:

Errors<-rbind(Mod3_Errors,Mod4_Errors)

rownames(Errors)<-c("Model with temperature","Model without temperature")

colnames(Errors)<-c("Residual Sum of Square","Residual Standard Error","Mean Squared Error","Relative error")

Errors<-as.data.frame(Errors)

maximum<-apply(Errors,2,max)

minimum<-apply(Errors,2,min)

Errors<-rbind(minimum,Errors)

Errors<-rbind(maximum,Errors)
radarchart(Errors,maxmin=TRUE,axistype=4,axislabcol="slategray4",
           centerzero=FALSE,seg=8,cglcol="gray67",
           pcol=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"),
           plty=1,
           plwd=3,
           title="Error comparison")

legend <-legend(1.5,1, legend=c("With temperature","Without temperature"),
                 seg.len=-1.4,
                 title="Errors",
                 pch=21, 
                 bty="n" ,lwd=3, y.intersp=1, horiz=FALSE,
                 col=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"))

Therefore, a model without temperature is better. But because temperature was included using the stepwise AIC criterion, and because its coefficient was significant (at an alpha of 10%), it will be considered for the day-to-day forecast

Forecast by country

For each of the following countries, the best suitable ARIMA model will be fitted, and its prediction power will be compared (a model with temperature and a model without)

The weather considered for these models will be the maximum presented and forecasted in AccuWeather

Republic of Costa Rica

Model with temperature

CostaRica_Temp<-read.csv("CostaRica_Temperature.csv",sep=";")
CostaRica_Temp$Date<-as.Date(CostaRica_Temp$Date,format="%d/%m/%Y")
COVID_2_Day_CostaRica<- COVID_2 %>% 
  filter(Country.Region %in% c("Costa Rica")) %>% 
  group_by(Date2) %>% summarise(World_confirmed=sum(Confirmed))

COVID_2_Day_CostaRica_Temp<-COVID_2_Day_CostaRica %>%
  inner_join(CostaRica_Temp,by=c("Date2"="Date"))

COVID_Day_series_CostaRica_Temp<-xts(COVID_2_Day_CostaRica_Temp$World_confirmed, order.by=COVID_2_Day_CostaRica_Temp$Date2)
COVID_series_CostaRica_Temp_Train<-COVID_Day_series_CostaRica_Temp[1:64] #Until March 25th
COVID_series_CostaRica_Temp_Test<-COVID_Day_series_CostaRica_Temp[65:length(COVID_Day_series_CostaRica_Temp)] #From March 26th onwards

Get the differentiated series: In this case until a second difference was suitable

plot(diff(diff(COVID_series_CostaRica_Temp_Train)),type="l",main="Costa Rica: 2nd difference") 

Correlograms of second diference:

tsdisplay(diff(diff(COVID_series_CostaRica_Temp_Train)), main="Costa Rica ACF and PACF correlograms")

By analyzing the autocorrelation and partial autocorrelation function plots. A model with 1 autoregressive term, and one moving average term will be fitted Arima model: ARIMA(1,2,1)

ARIMA1_CostaRica<-Arima(COVID_series_CostaRica_Temp_Train,order=c(1,2,1),xreg=COVID_2_Day_CostaRica_Temp$Temperature_CostaRica[1:64])

summary(ARIMA1_CostaRica)
Series: COVID_series_CostaRica_Temp_Train 
Regression with ARIMA(1,2,1) errors 

Coefficients:
          ar1     ma1     xreg
      -0.7967  0.4328  -0.2134
s.e.   0.1462  0.1840   0.1159

sigma^2 estimated as 7.227:  log likelihood=-147.93
AIC=303.86   AICc=304.57   BIC=312.37

Training set error measures:
                    ME     RMSE      MAE MPE MAPE      MASE         ACF1
Training set 0.4281342 2.581091 1.397242 NaN  Inf 0.4379416 -0.009839234

Predictive error functions:

#Relative error
RE <- function(Fore,Actual) {
  return(sum(abs(Fore-Actual))/abs(sum(Actual)))
}


#MAPE
MAPE<-function(Fore,Actual){
  return(
    mean(abs(Actual-Fore)/abs(Actual))*100
    )
}

# mean squared error (MSE)
MSE<-function(Fore,Actual) {
  N<-length(Actual)
  ss<-sum((Actual-Fore)^2)
  return((1/N)*ss)
}

#PFA
PFA <- function(Fore,Actual) {
  Total<-0
  N<-length(Fore)
  for(i in 1:N) {
    if(Fore[i]>=Actual[i])
      Total<-Total+1      
  }
  return(Total/N)
}

Forecast prediction to compare:

Test_CostaRica_Temp<-COVID_2_Day_CostaRica_Temp$Temperature_CostaRica[65:length(COVID_Day_series_CostaRica_Temp)]


P_CRI_1<-forecast(ARIMA1_CostaRica,xreg = Test_CostaRica_Temp)

Calculate errors:

RE_CRI_1<-RE(P_CRI_1$mean,as.vector(COVID_series_CostaRica_Temp_Test))
MAPE_CRI_1<-MAPE(P_CRI_1$mean,as.vector(COVID_series_CostaRica_Temp_Test))
MSE_CRI_1<-MSE(P_CRI_1$mean,as.vector(COVID_series_CostaRica_Temp_Test))
PFA_CRI_1<-PFA(P_CRI_1$mean,as.vector(COVID_series_CostaRica_Temp_Test))

Errors.CRI_1<-c(RE_CRI_1,MAPE_CRI_1,MSE_CRI_1,PFA_CRI_1)
Errors.CRI_1
[1]   0.03098311   3.43225773 252.77719656   0.45000000

Model without temperature

ARIMA(1,2,0)

ARIMA2_CostaRica<-Arima(COVID_series_CostaRica_Temp_Train,order=c(1,2,0))

summary(ARIMA2_CostaRica)
Series: COVID_series_CostaRica_Temp_Train 
ARIMA(1,2,0) 

Coefficients:
          ar1
      -0.4701
s.e.   0.1133

sigma^2 estimated as 7.607:  log likelihood=-150.5
AIC=304.99   AICc=305.19   BIC=309.24

Training set error measures:
                    ME     RMSE      MAE     MPE     MAPE      MASE       ACF1
Training set 0.5145597 2.692615 1.200452 7.86228 21.76863 0.3762612 0.04160954

Forecast prediction to compare

P_CRI_2<-forecast(ARIMA2_CostaRica,h=length(COVID_series_CostaRica_Temp_Test))

Calculate errors:

RE_CRI_2<-RE(P_CRI_2$mean,as.vector(COVID_series_CostaRica_Temp_Test))
MAPE_CRI_2<-MAPE(P_CRI_2$mean,as.vector(COVID_series_CostaRica_Temp_Test))
MSE_CRI_2<-MSE(P_CRI_2$mean,as.vector(COVID_series_CostaRica_Temp_Test))
PFA_CRI_2<-PFA(P_CRI_2$mean,as.vector(COVID_series_CostaRica_Temp_Test))

Errors.CRI_2<-c(RE_CRI_2,MAPE_CRI_2,MSE_CRI_2,PFA_CRI_2)
Errors.CRI_2
[1]   0.0291204   3.3461353 227.1872124   0.4000000
Errors<-rbind(Errors.CRI_1,Errors.CRI_2)

rownames(Errors)<-c("ARIMAX(1,2,1)","ARIMA(1,2,0)")

colnames(Errors)<-c("Relative error","Mean Abs % error","Mean Squared Error","PFA")

Errors<-as.data.frame(Errors)

maximum<-apply(Errors,2,max)

minimum<-apply(Errors,2,min)

Errors<-rbind(minimum,Errors)

Errors<-rbind(maximum,Errors)
radarchart(Errors,maxmin=TRUE,axistype=4,axislabcol="slategray4",
           centerzero=FALSE,seg=8,cglcol="gray67",
           pcol=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"),
           plty=1,
           plwd=3,
           title="Costa Rica forecast: error comparison on test set")

legend <-legend(1.5,1, legend=c("ARIMAX(1,2,1)","ARIMA(1,2,0)"),
                 seg.len=-1.4,
                 title="Errors",
                 pch=21, 
                 bty="n" ,lwd=3, y.intersp=1, horiz=FALSE,
                 col=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"))

Conclusion: Keep model without temperature

Make model with the overall series

ARIMA_Final_CostaRica<-Arima(COVID_Day_series_CostaRica_Temp,order=c(0,2,1))
summary(ARIMA_Final_CostaRica)
Series: COVID_Day_series_CostaRica_Temp 
ARIMA(0,2,1) 

Coefficients:
          ma1
      -0.3824
s.e.   0.1256

sigma^2 estimated as 20.06:  log likelihood=-238.87
AIC=481.75   AICc=481.9   BIC=486.56

Training set error measures:
                    ME    RMSE      MAE      MPE     MAPE      MASE       ACF1
Training set 0.2023214 4.39795 2.334426 3.912512 11.69981 0.3135232 0.02228771

Final forecast:

Future_CostaRica_Temp<-CostaRica_Temp$Temperature_CostaRica[CostaRica_Temp$Date>max(COVID_2$Date2)]

P_CRI_Final<-forecast(ARIMA_Final_CostaRica,h = length(Future_CostaRica_Temp))
Low_lim_CRI<-data.frame(P_CRI_Final$lower)[,2]
Upp_lim_CRI<-data.frame(P_CRI_Final$upper)[,2]

For making the plot:

##Data periods
per_1 <- as.Date(as.character(COVID_2_Day_CostaRica_Temp$Date2))
per_2 <- seq(as.Date(max(COVID_2_Day_CostaRica_Temp$Date2)+1,format="%Y-%m-%d"), as.Date("2020-04-30",format="%Y-%m-%d"),"1 day")


# Merge forecast + actuals
data <- xts(COVID_Day_series_CostaRica_Temp,order.by=per_1) 
dataNA <- rep(NA, length(data))
A <- cbind(data,dataNA,dataNA,dataNA)


Low_lim_CRI <- xts(Low_lim_CRI,order.by=per_2)
Forecast_CRI <- xts(P_CRI_Final$mean,order.by=per_2)
Upp_lim_CRI <- xts(Upp_lim_CRI,order.by=per_2)
predNA <- rep(NA, length(Forecast_CRI))
B <- cbind(predNA, Low_lim_CRI, Forecast_CRI, Upp_lim_CRI)

all_series_CRI <- data.frame(rbind(as.matrix(A),as.matrix(B)))
colnames(all_series_CRI) <- c('Actual', 'Lower_limit', 'Forecast', 'Upper_limit')
dygraph(all_series_CRI, main="SARS-COV2-outbreak: Total Costa Rica cases",xlab="Date", ylab="Novel coronavirus cases",width = 750)%>%
  dySeries(c('Lower_limit', 'Forecast', 'Upper_limit'),label="Forecast",strokeWidth=2,
           drawPoints = TRUE, pointSize = 2, color=rgb(189/255,44/255,47/255)) %>%
  dySeries("Actual",drawPoints = TRUE, strokeWidth=2, pointSize = 2,
           color=rgb(10/255,44/255,119/255)) %>% 
  dyRangeSelector()

NA

Mexico

Model with temperature

Mexico_Temp<-read.csv("Mexico_Temperature.csv",sep=";")
Mexico_Temp$Date<-as.Date(Mexico_Temp$Date,format="%d/%m/%Y")
COVID_2_Day_Mexico<- COVID_2 %>% 
  filter(Country.Region %in% c("Mexico")) %>% 
  group_by(Date2) %>% summarise(World_confirmed=sum(Confirmed))

COVID_2_Day_Mexico_Temp<-COVID_2_Day_Mexico %>%
  inner_join(Mexico_Temp,by=c("Date2"="Date"))

COVID_Day_series_Mexico_Temp<-xts(COVID_2_Day_Mexico_Temp$World_confirmed, order.by=COVID_2_Day_Mexico_Temp$Date2)
COVID_series_Mexico_Train<-COVID_Day_series_Mexico_Temp[1:74] #Until April 4th
COVID_series_Mexico_Test<-COVID_Day_series_Mexico_Temp[75:length(COVID_Day_series_Mexico_Temp)] #From April 5th onwards

Get the differentiated series: In this case until a second difference was suitable

plot(diff(diff(COVID_series_Mexico_Train)),type="l",main="Mexico: 2nd difference") 

Correlograms of second diference:

tsdisplay(diff(diff(COVID_series_Mexico_Train)),main="Mexico ACF and PACF correlograms")

By analyzing the autocorrelation and partial autocorrelation function plots. A model with 2 autoregressive terms, and 3 moving average terms will be fitted

Arima model: ARIMA(2,2,3)

ARIMA1_Mexico<-Arima(COVID_series_Mexico_Train,order=c(2,2,3),xreg=COVID_2_Day_Mexico_Temp$Temperature_Mexico[1:74])
summary(ARIMA1_Mexico)
Series: COVID_series_Mexico_Train 
Regression with ARIMA(2,2,3) errors 

Coefficients:
          ar1      ar2     ma1     ma2     ma3    xreg
      -0.9027  -0.7333  1.1212  0.8938  0.6734  0.0623
s.e.   0.1121   0.1592  0.1093  0.2027  0.1153  0.2567

sigma^2 estimated as 123.3:  log likelihood=-273.92
AIC=561.84   AICc=563.59   BIC=577.78

Training set error measures:
                   ME     RMSE      MAE MPE MAPE      MASE       ACF1
Training set 1.716467 10.48796 5.177678 NaN  Inf 0.2239162 -0.1047828

Forecast prediction to compare

Test_Mexico_Temp<-COVID_2_Day_Mexico_Temp$Temperature_Mexico[75:length(COVID_Day_series_Mexico_Temp)]


P_MEX_1<-forecast(ARIMA1_Mexico,xreg = Test_Mexico_Temp)

Calculate errors:

RE_MEX_1<-RE(P_MEX_1$mean,as.vector(COVID_series_Mexico_Test))
MAPE_MEX_1<-MAPE(P_MEX_1$mean,as.vector(COVID_series_Mexico_Test))
MSE_MEX_1<-MSE(P_MEX_1$mean,as.vector(COVID_series_Mexico_Test))
PFA_MEX_1<-PFA(P_MEX_1$mean,as.vector(COVID_series_Mexico_Test))

Errors.MEX_1<-c(RE_MEX_1,MAPE_MEX_1,MSE_MEX_1,PFA_MEX_1)
Errors.MEX_1
[1] 2.067523e-01 1.776974e+01 7.367257e+05 0.000000e+00

Model without temperature

ARIMA(2,2,4)

ARIMA2_Mexico<-Arima(COVID_series_Mexico_Train,order=c(2,2,4))

summary(ARIMA2_Mexico)
Series: COVID_series_Mexico_Train 
ARIMA(2,2,4) 

Coefficients:
          ar1      ar2     ma1     ma2     ma3      ma4
      -0.7232  -0.7137  0.8661  0.6669  0.4019  -0.3260
s.e.   0.1415   0.1688  0.1697  0.2135  0.1529   0.1463

sigma^2 estimated as 116.5:  log likelihood=-272.23
AIC=558.45   AICc=560.2   BIC=574.39

Training set error measures:
                   ME     RMSE     MAE      MPE     MAPE      MASE        ACF1
Training set 2.187346 10.19436 4.88144 4.696633 18.33364 0.2111049 -0.05173888

Forecast prediction to compare

P_MEX_2<-forecast(ARIMA2_Mexico,h=length(COVID_series_Mexico_Test))

Calculate errors:

RE_MEX_2<-RE(P_MEX_2$mean,as.vector(COVID_series_Mexico_Test))
MAPE_MEX_2<-MAPE(P_MEX_2$mean,as.vector(COVID_series_Mexico_Test))
MSE_MEX_2<-MSE(P_MEX_2$mean,as.vector(COVID_series_Mexico_Test))
PFA_MEX_2<-PFA(P_MEX_2$mean,as.vector(COVID_series_Mexico_Test))

Errors.MEX_2<-c(RE_MEX_2,MAPE_MEX_2,MSE_MEX_2,PFA_MEX_2)
Errors.MEX_2
[1] 2.121065e-01 1.822148e+01 7.758630e+05 0.000000e+00
Errors<-rbind(Errors.MEX_1,Errors.MEX_2)

rownames(Errors)<-c("ARIMAX(2,2,3)","ARIMA(2,2,4)")

colnames(Errors)<-c("Relative error","Mean Abs % error","Mean Squared Error","PFA")

Errors<-as.data.frame(Errors)

maximum<-apply(Errors,2,max)

minimum<-apply(Errors,2,min)

Errors<-rbind(minimum,Errors)

Errors<-rbind(maximum,Errors)
radarchart(Errors,maxmin=TRUE,axistype=4,axislabcol="slategray4",
           centerzero=FALSE,seg=8,cglcol="gray67",
           pcol=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"),
           plty=1,
           plwd=3,
           title="Mexico forecast: error comparison on test set")

legend <-legend(1.5,1, legend=c("ARIMAX(2,2,3)","ARIMA(2,2,4)"),
                 seg.len=-1.4,
                 title="Errors",
                 pch=21, 
                 bty="n" ,lwd=3, y.intersp=1, horiz=FALSE,
                 col=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"))

Conclusion: Keep model with temperature

Make model with the overall series

ARIMA_Final_Mexico<-Arima(COVID_Day_series_Mexico_Temp,order=c(2,2,1),xreg=COVID_2_Day_Mexico_Temp$Temperature_Mexico)

summary(ARIMA_Final_Mexico)
Series: COVID_Day_series_Mexico_Temp 
Regression with ARIMA(2,2,1) errors 

Coefficients:
         ar1     ar2      ma1     xreg
      0.3938  0.5755  -0.8956  -0.1913
s.e.  0.1076  0.1022   0.0648   1.0722

sigma^2 estimated as 693.1:  log likelihood=-383.01
AIC=776.01   AICc=776.8   BIC=788.05

Training set error measures:
                   ME     RMSE     MAE MPE MAPE      MASE        ACF1
Training set 3.482614 25.36974 11.9289 NaN  Inf 0.1974668 -0.02342905

Final forecast:

Future_Mexico_Temp<-Mexico_Temp$Temperature_Mexico[Mexico_Temp$Date>max(COVID_2$Date2)]

P_MEX_Final<-forecast(ARIMA_Final_Mexico,xreg = Future_Mexico_Temp)
Low_lim_MEX<-data.frame(P_MEX_Final$lower)[,2]
Upp_lim_MEX<-data.frame(P_MEX_Final$upper)[,2]

For making the plot:

##Data periods
per_1 <- as.Date(as.character(COVID_2_Day_Mexico_Temp$Date2))
per_2 <- seq(as.Date(max(COVID_2_Day_Mexico_Temp$Date2)+1,format="%Y-%m-%d"), as.Date("2020-04-30",format="%Y-%m-%d"),"1 day")


# Merge forecast + actuals
data <- xts(COVID_Day_series_Mexico_Temp,order.by=per_1) 
dataNA <- rep(NA, length(data))
A <- cbind(data,dataNA,dataNA,dataNA)


Low_lim_MEX <- xts(Low_lim_MEX,order.by=per_2)
Forecast_MEX <- xts(P_MEX_Final$mean,order.by=per_2)
Upp_lim_MEX <- xts(Upp_lim_MEX,order.by=per_2)
predNA <- rep(NA, length(Forecast_MEX))
B <- cbind(predNA, Low_lim_MEX, Forecast_MEX, Upp_lim_MEX)

all_series_MEX <- data.frame(rbind(as.matrix(A),as.matrix(B)))
colnames(all_series_MEX) <- c('Actual', 'Lower_limit', 'Forecast', 'Upper_limit')
dygraph(all_series_MEX, main="SARS-COV2-outbreak: Total Mexico cases",xlab="Date", ylab="Novel coronavirus cases",width = 750)%>%
  dySeries(c('Lower_limit', 'Forecast', 'Upper_limit'),label="Forecast",strokeWidth=2,
           drawPoints = TRUE, pointSize = 2, color=rgb(189/255,44/255,47/255)) %>%
  dySeries("Actual",drawPoints = TRUE, strokeWidth=2, pointSize = 2,
           color=rgb(43/255,102/255,73/255)) %>% 
  dyRangeSelector()

NA

Italy

Model with temperature

Italy_Temp<-read.csv("Italy_Temperature.csv",sep=";")
Italy_Temp$Date<-as.Date(Italy_Temp$Date,format="%d/%m/%Y")
COVID_2_Day_Italy<- COVID_2 %>% 
  filter(Country.Region %in% c("Italy")) %>% 
  group_by(Date2) %>% summarise(World_confirmed=sum(Confirmed))

COVID_2_Day_Italy_Temp<-COVID_2_Day_Italy %>%
  inner_join(Italy_Temp,by=c("Date2"="Date"))

COVID_Day_series_Italy_Temp<-xts(COVID_2_Day_Italy_Temp$World_confirmed, order.by=COVID_2_Day_Italy_Temp$Date2)
COVID_Day_series_Italy_Train<-COVID_Day_series_Italy_Temp[1:64] #Until March 25th
COVID_Day_series_Italy_Test<-COVID_Day_series_Italy_Temp[65:length(COVID_Day_series_Italy_Temp)] #From March 26th onwards

Get the lagged difference:

plot(diff(diff(COVID_Day_series_Italy_Train)),type="l",main="Italy: 2nd difference") 

Correlograms of first diference:

tsdisplay(diff(diff(COVID_Day_series_Italy_Train)),main="Italy ACF and PACF correlograms")

By analyzing the autocorrelation and partial autocorrelation function plots. A model with 1 autoregressive term will be fitted.

Arima model: ARIMA(1,2,0)

ARIMA1_Italy<-auto.arima(COVID_Day_series_Italy_Train,
                             xreg = COVID_2_Day_Italy_Temp$Temperature_Italy[1:64])

ARIMA1_Italy<-Arima(COVID_Day_series_Italy_Train,order=c(1,2,0),xreg=COVID_2_Day_Italy_Temp$Temperature_Italy[1:64])

summary(ARIMA1_Italy)
Series: COVID_Day_series_Italy_Train 
Regression with ARIMA(1,2,0) errors 

Coefficients:
          ar1     xreg
      -0.5094  21.4065
s.e.   0.1119  20.2612

sigma^2 estimated as 489396:  log likelihood=-493.24
AIC=992.47   AICc=992.89   BIC=998.85

Training set error measures:
                   ME     RMSE      MAE MPE MAPE      MASE         ACF1
Training set 123.6808 677.3545 335.2238 NaN  Inf 0.2839123 -0.005148955

Forecast prediction to compare

Test_Italy_Temp<-COVID_2_Day_Italy_Temp$Temperature_Italy[65:length(COVID_Day_series_Italy_Temp)]


P_ITA_1<-forecast(ARIMA1_Italy,xreg = Test_Italy_Temp)

Calculate errors:

RE_ITA_1<-RE(P_ITA_1$mean,as.vector(COVID_Day_series_Italy_Test))
MAPE_ITA_1<-MAPE(P_ITA_1$mean,as.vector(COVID_Day_series_Italy_Test))
MSE_ITA_1<-MSE(P_ITA_1$mean,as.vector(COVID_Day_series_Italy_Test))
PFA_ITA_1<-PFA(P_ITA_1$mean,as.vector(COVID_Day_series_Italy_Test))

Errors.ITA_1<-c(RE_ITA_1,MAPE_ITA_1,MSE_ITA_1,PFA_ITA_1)
Errors.ITA_1
[1] 4.619212e-02 4.067575e+00 6.062428e+07 7.500000e-01

Model without temperature

ARIMA(1,2,0)

ARIMA2_Italy<-Arima(COVID_Day_series_Italy_Train,order=c(1,2,0))
summary(ARIMA2_Italy)
Series: COVID_Day_series_Italy_Train 
ARIMA(1,2,0) 

Coefficients:
          ar1
      -0.5414
s.e.   0.1046

sigma^2 estimated as 489700:  log likelihood=-493.79
AIC=991.58   AICc=991.79   BIC=995.84

Training set error measures:
                   ME     RMSE     MAE      MPE     MAPE      MASE        ACF1
Training set 125.8084 683.1877 295.442 5.335548 11.24576 0.2502197 -0.02348722

Forecast prediction to compare

P_ITA_2<-forecast(ARIMA2_Italy,h=length(COVID_Day_series_Italy_Test))

Calculate errors:

RE_ITA_2<-RE(P_ITA_2$mean,as.vector(COVID_Day_series_Italy_Test))
MAPE_ITA_2<-MAPE(P_ITA_2$mean,as.vector(COVID_Day_series_Italy_Test))
MSE_ITA_2<-MSE(P_ITA_2$mean,as.vector(COVID_Day_series_Italy_Test))
PFA_ITA_2<-PFA(P_ITA_2$mean,as.vector(COVID_Day_series_Italy_Test))

Errors.ITA_2<-c(RE_ITA_2,MAPE_ITA_2,MSE_ITA_2,PFA_ITA_2)
Errors.ITA_2
[1] 4.366177e-02 3.858628e+00 5.437200e+07 7.000000e-01
Errors<-rbind(Errors.ITA_1,Errors.ITA_2)

rownames(Errors)<-c("ARIMAX(1,2,0)","ARIMA(1,2,0)")

colnames(Errors)<-c("Relative error","Mean Abs % error","Mean Squared Error","PFA")

Errors<-as.data.frame(Errors)

maximum<-apply(Errors,2,max)

minimum<-apply(Errors,2,min)

Errors<-rbind(minimum,Errors)

Errors<-rbind(maximum,Errors)
radarchart(Errors,maxmin=TRUE,axistype=4,axislabcol="slategray4",
           centerzero=FALSE,seg=8,cglcol="gray67",
           pcol=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"),
           plty=1,
           plwd=3,
           title="Italy forecast: error comparison on test set")

legend <-legend(1.5,1, legend=c("ARIMAX(1,2,0)","ARIMA(1,2,0)"),
                 seg.len=-1.4,
                 title="Errors",
                 pch=21, 
                 bty="n" ,lwd=3, y.intersp=1, horiz=FALSE,
                 col=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"))

Conclusion: Keep model without temperature

Make model with the overall series

ARIMA_Final_Italy<-Arima(COVID_Day_series_Italy_Temp,order=c(1,2,0))
summary(ARIMA_Final_Italy)
Series: COVID_Day_series_Italy_Temp 
ARIMA(1,2,0) 

Coefficients:
          ar1
      -0.4466
s.e.   0.0977

sigma^2 estimated as 492850:  log likelihood=-653.39
AIC=1310.77   AICc=1310.93   BIC=1315.59

Training set error measures:
                  ME     RMSE      MAE      MPE     MAPE      MASE       ACF1
Training set 52.1456 689.3822 355.6425 3.725906 8.274914 0.1816647 0.01556872

Final forecast:

Future_Italy_Temp<-Italy_Temp$Temperature_Italy[Italy_Temp$Date>max(COVID_2$Date2)]

P_ITA_Final<-forecast(ARIMA_Final_Italy,h=length(Future_Italy_Temp))
Low_lim_ITA<-data.frame(P_ITA_Final$lower)[,2]
Upp_lim_ITA<-data.frame(P_ITA_Final$upper)[,2]

For making the plot:

##Data periods
per_1 <- as.Date(as.character(COVID_2_Day_Italy_Temp$Date2))
per_2 <- seq(as.Date(max(COVID_2_Day_Italy_Temp$Date2)+1,format="%Y-%m-%d"), as.Date("2020-04-30",format="%Y-%m-%d"),"1 day")


# Merge forecast + actuals
data <- xts(COVID_Day_series_Italy_Temp,order.by=per_1) 
dataNA <- rep(NA, length(data))
A <- cbind(data,dataNA,dataNA,dataNA)


Low_lim_ITA <- xts(Low_lim_ITA,order.by=per_2)
Forecast_ITA <- xts(P_ITA_Final$mean,order.by=per_2)
Upp_lim_ITA <- xts(Upp_lim_ITA,order.by=per_2)
predNA <- rep(NA, length(Forecast_ITA))
B <- cbind(predNA, Low_lim_ITA, Forecast_ITA, Upp_lim_ITA)

all_series_ITA <- data.frame(rbind(as.matrix(A),as.matrix(B)))
colnames(all_series_ITA) <- c('Actual', 'Lower_limit', 'Forecast', 'Upper_limit')
dygraph(all_series_ITA, main="SARS-COV2-outbreak: Total Italy cases",xlab="Date", ylab="Novel coronavirus cases",width = 750)%>%
  dySeries(c('Lower_limit', 'Forecast', 'Upper_limit'),label="Forecast",strokeWidth=2,
           drawPoints = TRUE, pointSize = 2, color=rgb(190/255,59/255,61/255)) %>%
  dySeries("Actual",drawPoints = TRUE, strokeWidth=2, pointSize = 2,
           color=rgb(64/255,143/255,78/255)) %>% 
  dyRangeSelector()

NA

Lebanon

Model with temperature

Lebanon_Temp<-read.csv("Lebanon_Temperature.csv",sep=";")
Lebanon_Temp$Date<-as.Date(Lebanon_Temp$Date,format="%d/%m/%Y")
COVID_2_Day_Lebanon<- COVID_2 %>% 
  filter(Country.Region %in% c("Lebanon")) %>% 
  group_by(Date2) %>% summarise(World_confirmed=sum(Confirmed))

COVID_2_Day_Lebanon_Temp<-COVID_2_Day_Lebanon %>%
  inner_join(Lebanon_Temp,by=c("Date2"="Date"))

COVID_Day_series_Lebanon_Temp<-xts(COVID_2_Day_Lebanon_Temp$World_confirmed, order.by=COVID_2_Day_Lebanon_Temp$Date2)
COVID_series_Lebanon_Train<-COVID_Day_series_Lebanon_Temp[1:74] #Until April 4th
COVID_series_Lebanon_Test<-COVID_Day_series_Lebanon_Temp[75:length(COVID_Day_series_Lebanon_Temp)] #From April 4th onwards

Get the differentiated series: In this case until a first difference was suitable

plot(diff(COVID_series_Lebanon_Train),type="l",main="Lebanon: 1st difference") 

Correlograms of first diference:

tsdisplay(diff(COVID_series_Lebanon_Train),main="Lebanon ACF and PACF correlograms")

Arima model: ARIMA(1,1,2)

ARIMA1_Lebanon<-Arima(COVID_series_Lebanon_Train,order=c(1,1,2),xreg=COVID_2_Day_Lebanon_Temp$Temperature_Lebanon[1:74])

summary(ARIMA1_Lebanon)
Series: COVID_series_Lebanon_Train 
Regression with ARIMA(1,1,2) errors 

Coefficients:
         ar1      ma1     ma2    xreg
      0.9216  -0.8504  0.4970  0.0461
s.e.  0.0488   0.0989  0.1263  0.2338

sigma^2 estimated as 60.69:  log likelihood=-252.36
AIC=514.72   AICc=515.62   BIC=526.17

Training set error measures:
                   ME     RMSE      MAE MPE MAPE      MASE        ACF1
Training set 1.101941 7.522299 3.444682 NaN  Inf 0.4835803 -0.03853757

Forecast prediction to compare

Test_Lebanon_Temp<-COVID_2_Day_Lebanon_Temp$Temperature_Lebanon[75:length(COVID_Day_series_Lebanon_Temp)]


P_LBN_1<-forecast(ARIMA1_Lebanon,xreg = Test_Lebanon_Temp)

Calculate errors:

RE_LBN_1<-RE(P_LBN_1$mean,as.vector(COVID_series_Lebanon_Test))
MAPE_LBN_1<-MAPE(P_LBN_1$mean,as.vector(COVID_series_Lebanon_Test))
MSE_LBN_1<-MSE(P_LBN_1$mean,as.vector(COVID_series_Lebanon_Test))
PFA_LBN_1<-PFA(P_LBN_1$mean,as.vector(COVID_series_Lebanon_Test))

Errors.LBN_1<-c(RE_LBN_1,MAPE_LBN_1,MSE_LBN_1,PFA_LBN_1)
Errors.LBN_1
[1]   0.03465457   3.32822038 607.23729449   0.30000000

Model without temperature

ARIMA(0,2,2)

#Auto Arima for Lebanon:
ARIMA2_Lebanon<-Arima(COVID_series_Lebanon_Train,order=c(0,2,2))

summary(ARIMA2_Lebanon)
Series: COVID_series_Lebanon_Train 
ARIMA(0,2,2) 

Coefficients:
          ma1     ma2
      -0.8655  0.4654
s.e.   0.0939  0.1264

sigma^2 estimated as 61.55:  log likelihood=-249.92
AIC=505.84   AICc=506.19   BIC=512.67

Training set error measures:
                   ME     RMSE      MAE      MPE     MAPE      MASE        ACF1
Training set 0.286841 7.630167 3.533028 2.334374 15.75454 0.4959827 -0.05944259

Forecast prediction to compare

P_LBN_2<-forecast(ARIMA2_Lebanon,h=length(COVID_series_Lebanon_Test))

Calculate errors:

RE_LBN_2<-RE(P_LBN_2$mean,as.vector(COVID_series_Lebanon_Test))
MAPE_LBN_2<-MAPE(P_LBN_2$mean,as.vector(COVID_series_Lebanon_Test))
MSE_LBN_2<-MSE(P_LBN_2$mean,as.vector(COVID_series_Lebanon_Test))
PFA_LBN_2<-PFA(P_LBN_2$mean,as.vector(COVID_series_Lebanon_Test))

Errors.LBN_2<-c(RE_LBN_2,MAPE_LBN_2,MSE_LBN_2,PFA_LBN_2)
Errors.LBN_2
[1]  0.01137244  1.13881038 55.27593453  0.60000000
Errors<-rbind(Errors.LBN_1,Errors.LBN_2)

rownames(Errors)<-c("ARIMAX(1,1,2)","ARIMA(0,2,2)")

colnames(Errors)<-c("Relative error","Mean Abs % error","Mean Squared Error","PFA")

Errors<-as.data.frame(Errors)

maximum<-apply(Errors,2,max)

minimum<-apply(Errors,2,min)

Errors<-rbind(minimum,Errors)

Errors<-rbind(maximum,Errors)
radarchart(Errors,maxmin=TRUE,axistype=4,axislabcol="slategray4",
           centerzero=FALSE,seg=8,cglcol="gray67",
           pcol=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"),
           plty=1,
           plwd=3,
           title="Lebanon forecast: error comparison on test set")

legend <-legend(1.5,1, legend=c("ARIMAX(1,1,2)","ARIMA(0,2,2)"),
                 seg.len=-1.4,
                 title="Errors",
                 pch=21, 
                 bty="n" ,lwd=3, y.intersp=1, horiz=FALSE,
                 col=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"))

Conclusion: Keep model without temperature

Make model with the overall series

ARIMA_Final_Lebanon<-Arima(COVID_Day_series_Lebanon_Temp,order=c(2,2,0))
summary(ARIMA_Final_Lebanon)
Series: COVID_Day_series_Lebanon_Temp 
ARIMA(2,2,0) 

Coefficients:
          ar1      ar2
      -0.9004  -0.2588
s.e.   0.1056   0.1055

sigma^2 estimated as 62.19:  log likelihood=-285.11
AIC=576.21   AICc=576.52   BIC=583.43

Training set error measures:
                    ME     RMSE      MAE      MPE     MAPE      MASE        ACF1
Training set 0.1624741 7.696121 3.788102 2.444889 12.73385 0.4905031 -0.01955309

Final forecast:

Future_Lebanon_Temp<-Lebanon_Temp$Temperature_Lebanon[Lebanon_Temp$Date>max(COVID_2$Date2)]

P_LBN_Final<-forecast(ARIMA_Final_Lebanon,h=length(Future_Lebanon_Temp))
Low_lim_LBN<-data.frame(P_LBN_Final$lower)[,2]
Upp_lim_LBN<-data.frame(P_LBN_Final$upper)[,2]

For making the plot:

##Data periods
per_1 <- as.Date(as.character(COVID_2_Day_Lebanon_Temp$Date2))
per_2 <- seq(as.Date(max(COVID_2_Day_Lebanon_Temp$Date2)+1,format="%Y-%m-%d"), as.Date("2020-04-30",format="%Y-%m-%d"),"1 day")


# Merge forecast + actuals
data <- xts(COVID_Day_series_Lebanon_Temp,order.by=per_1) 
dataNA <- rep(NA, length(data))
A <- cbind(data,dataNA,dataNA,dataNA)


Low_lim_LBN <- xts(Low_lim_LBN,order.by=per_2)
Forecast_LBN <- xts(P_LBN_Final$mean,order.by=per_2)
Upp_lim_LBN <- xts(Upp_lim_LBN,order.by=per_2)
predNA <- rep(NA, length(Forecast_ITA))
B <- cbind(predNA, Low_lim_LBN, Forecast_LBN, Upp_lim_LBN)

all_series_LBN <- data.frame(rbind(as.matrix(A),as.matrix(B)))
colnames(all_series_LBN) <- c('Actual', 'Lower_limit', 'Forecast', 'Upper_limit')
dygraph(all_series_LBN, main="SARS-COV2-outbreak: Total Lebanon cases",xlab="Date", ylab="Novel coronavirus cases",width = 750)%>%
  dySeries(c('Lower_limit', 'Forecast', 'Upper_limit'),label="Forecast",strokeWidth=2,
           drawPoints = TRUE, pointSize = 2, color=rgb(218/255,55/255,50/255)) %>%
  dySeries("Actual",drawPoints = TRUE, strokeWidth=2, pointSize = 2,
           color=rgb(73/255,163/255,90/255)) %>% 
  dyRangeSelector()

NA

Colombia

Model with temperature

Colombia_Temp<-read.csv("Colombia_Temperature.csv",sep=";")
Colombia_Temp$Date<-as.Date(Colombia_Temp$Date,format="%d/%m/%Y")
COVID_2_Day_Colombia<- COVID_2 %>% 
  filter(Country.Region %in% c("Colombia")) %>% 
  group_by(Date2) %>% summarise(World_confirmed=sum(Confirmed))

COVID_2_Day_Colombia_Temp<-COVID_2_Day_Colombia %>%
  inner_join(Colombia_Temp,by=c("Date2"="Date"))

COVID_Day_series_Colombia_Temp<-xts(COVID_2_Day_Colombia_Temp$World_confirmed, order.by=COVID_2_Day_Colombia_Temp$Date2)
COVID_series_Colombia_Train<-COVID_Day_series_Colombia_Temp[1:74] #Until April 4th
COVID_series_Colombia_Test<-COVID_Day_series_Colombia_Temp[75:length(COVID_Day_series_Colombia_Temp)] #From April 5th onwards

Get the differentiated series: In this case until a second difference was suitable

plot(diff(diff(COVID_series_Colombia_Train)),type="l",main="Colombia: 2nd difference") 

Correlograms of second diference:

tsdisplay(diff(diff(COVID_series_Colombia_Train)), main="Colombia ACF and PACF correlograms")

By analyzing the autocorrelation and partial autocorrelation function plots. A model with 2 autoregressive terms, and 2 moving average terms will be fitted

Arimax model: ARIMAX(2,2,2)

ARIMA1_Colombia<-Arima(COVID_series_Colombia_Train,order=c(2,2,2),xreg=COVID_2_Day_Colombia_Temp$Temperature_Colombia[1:74])

summary(ARIMA1_Colombia)
Series: COVID_series_Colombia_Train 
Regression with ARIMA(2,2,2) errors 

Coefficients:
          ar1      ar2      ma1     ma2     xreg
      -0.2675  -0.8894  -0.0039  0.6242  -0.2343
s.e.   0.1037   0.1016   0.1495  0.1732   0.5167

sigma^2 estimated as 217.7:  log likelihood=-294.03
AIC=600.06   AICc=601.35   BIC=613.72

Training set error measures:
                   ME     RMSE      MAE MPE MAPE      MASE        ACF1
Training set 2.197353 14.04005 6.409043 NaN  Inf 0.3327597 -0.07966735

Forecast prediction to compare

Test_Colombia_Temp<-COVID_2_Day_Colombia_Temp$Temperature_Colombia[75:length(COVID_Day_series_Colombia_Temp)]


P_COL_1<-forecast(ARIMA1_Colombia,xreg = Test_Colombia_Temp)

Calculate errors:

RE_COL_1<-RE(P_COL_1$mean,as.vector(COVID_series_Colombia_Test))
MAPE_COL_1<-MAPE(P_COL_1$mean,as.vector(COVID_series_Colombia_Test))
MSE_COL_1<-MSE(P_COL_1$mean,as.vector(COVID_series_Colombia_Test))
PFA_COL_1<-PFA(P_COL_1$mean,as.vector(COVID_series_Colombia_Test))

Errors.COL_1<-c(RE_COL_1,MAPE_COL_1,PFA_COL_1,MSE_COL_1)
Errors.COL_1
[1] 1.038332e-01 9.386524e+00 2.000000e-01 7.935003e+04

Model without temperature

ARIMA(2,2,2)

ARIMA2_Colombia<-Arima(COVID_series_Colombia_Train,order=c(2,2,2))

summary(ARIMA2_Colombia)
Series: COVID_series_Colombia_Train 
ARIMA(2,2,2) 

Coefficients:
          ar1      ar2     ma1     ma2
      -0.2672  -0.8913  0.0014  0.6244
s.e.   0.1037   0.1025  0.1486  0.1753

sigma^2 estimated as 215.1:  log likelihood=-294.13
AIC=598.26   AICc=599.17   BIC=609.65

Training set error measures:
                   ME     RMSE      MAE      MPE     MAPE      MASE        ACF1
Training set 2.199355 14.05992 6.130841 5.459672 19.97005 0.3183153 -0.08053967

Forecast prediction to compare

P_COL_2<-forecast(ARIMA2_Colombia,h=length(COVID_series_Colombia_Test))

Calculate errors:

RE_COL_2<-RE(P_COL_2$mean,as.vector(COVID_series_Colombia_Test))
MAPE_COL_2<-MAPE(P_COL_2$mean,as.vector(COVID_series_Colombia_Test))
MSE_COL_2<-MSE(P_COL_2$mean,as.vector(COVID_series_Colombia_Test))
PFA_COL_2<-PFA(P_COL_2$mean,as.vector(COVID_series_Colombia_Test))

Errors.COL_2<-c(RE_COL_2,MAPE_COL_2,MSE_COL_2,PFA_COL_2)
Errors.COL_2
[1]     0.103347     9.346102 78591.647234     0.200000
Errors<-rbind(Errors.COL_1,Errors.COL_2)

rownames(Errors)<-c("ARIMAX(2,2,2)","ARIMA(2,2,2)")

colnames(Errors)<-c("Relative error","Mean Abs % error","Mean Squared Error","PFA")

Errors<-as.data.frame(Errors)

maximum<-apply(Errors,2,max)

minimum<-apply(Errors,2,min)

Errors<-rbind(minimum,Errors)

Errors<-rbind(maximum,Errors)
radarchart(Errors,maxmin=TRUE,axistype=4,axislabcol="slategray4",
           centerzero=FALSE,seg=8,cglcol="gray67",
           pcol=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"),
           plty=1,
           plwd=3,
           title="Colombia forecast: error comparison on test set")

legend <-legend(1.5,1, legend=c("ARIMAX(2,2,2)","ARIMA(2,2,2)"),
                 seg.len=-1.4,
                 title="Errors",
                 pch=21, 
                 bty="n" ,lwd=3, y.intersp=1, horiz=FALSE,
                 col=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"))

Conclusion: Keep model without temperature

Make model with the overall series

ARIMA_Final_Colombia<-Arima(COVID_Day_series_Colombia_Temp,order=c(5,2,1))


summary(ARIMA_Final_Colombia)
Series: COVID_Day_series_Colombia_Temp 
ARIMA(5,2,1) 

Coefficients:
         ar1      ar2     ar3      ar4      ar5      ma1
      0.1801  -0.2926  0.2750  -0.2965  -0.4795  -0.3580
s.e.  0.1445   0.0999  0.1248   0.1166   0.1178   0.1353

sigma^2 estimated as 603:  log likelihood=-377.42
AIC=768.85   AICc=770.36   BIC=785.69

Training set error measures:
                   ME     RMSE      MAE      MPE     MAPE      MASE        ACF1
Training set 4.289777 23.35663 11.44386 5.451225 17.60467 0.3188454 -0.08898061

Final forecast:

Future_Colombia_Temp<-Colombia_Temp$Temperature_Colombia[Colombia_Temp$Date>max(COVID_2$Date2)]

P_COL_Final<-forecast(ARIMA_Final_Colombia,h=length(Future_Colombia_Temp))
Low_lim_COL<-data.frame(P_COL_Final$lower)[,2]
Upp_lim_COL<-data.frame(P_COL_Final$upper)[,2]

For making the plot:

##Data periods
per_1 <- as.Date(as.character(COVID_2_Day_Colombia_Temp$Date2))
per_2 <- seq(as.Date(max(COVID_2_Day_Colombia_Temp$Date2)+1,format="%Y-%m-%d"), as.Date("2020-04-30",format="%Y-%m-%d"),"1 day")


# Merge forecast + actuals
data <- xts(COVID_Day_series_Colombia_Temp,order.by=per_1) 
dataNA <- rep(NA, length(data))
A <- cbind(data,dataNA,dataNA,dataNA)


Low_lim_COL <- xts(Low_lim_COL,order.by=per_2)
Forecast_COL <- xts(P_COL_Final$mean,order.by=per_2)
Upp_lim_COL <- xts(Upp_lim_COL,order.by=per_2)
predNA <- rep(NA, length(Forecast_COL))
B <- cbind(predNA, Low_lim_COL, Forecast_COL, Upp_lim_COL)

all_series_COL <- data.frame(rbind(as.matrix(A),as.matrix(B)))
colnames(all_series_COL) <- c('Actual', 'Lower_limit', 'Forecast', 'Upper_limit')
dygraph(all_series_COL, main="SARS-COV2-outbreak: Total Colombia cases",xlab="Date", ylab="Novel coronavirus cases",width = 750)%>%
  dySeries(c('Lower_limit', 'Forecast', 'Upper_limit'),label="Forecast",strokeWidth=2,
           drawPoints = TRUE, pointSize = 2, color=rgb(17/255,57/255,141/255)) %>%
  dySeries("Actual",drawPoints = TRUE, strokeWidth=2, pointSize = 2,
           color=rgb(246/255,209/255,75/255)) %>% 
  dyRangeSelector()

NA

Chile

Model with temperature

Chile_Temp<-read.csv("Chile_Temperature.csv",sep=";")
Chile_Temp$Date<-as.Date(Chile_Temp$Date,format="%d/%m/%Y")
COVID_2_Day_Chile<- COVID_2 %>% 
  filter(Country.Region %in% c("Chile")) %>% 
  group_by(Date2) %>% summarise(World_confirmed=sum(Confirmed))

COVID_2_Day_Chile_Temp<-COVID_2_Day_Chile %>%
  inner_join(Chile_Temp,by=c("Date2"="Date"))

COVID_Day_series_Chile_Temp<-xts(COVID_2_Day_Chile_Temp$World_confirmed, order.by=COVID_2_Day_Chile_Temp$Date2)
COVID_series_Chile_Train<-COVID_Day_series_Chile_Temp[1:76] #Until April 6th
COVID_series_Chile_Test<-COVID_Day_series_Chile_Temp[77:length(COVID_Day_series_Chile_Temp)] #From April 7th onwards

Get the differentiated series: In this case a first difference was suitable

plot(diff(COVID_series_Chile_Train),type="l",main="Chile: 1st difference") 

Correlograms of first diference:

tsdisplay(diff(COVID_series_Chile_Train),main="Chile ACF and PACF correlograms")

By analyzing the autocorrelation and partial autocorrelation function plots. A model with 1 autoregressive term will be fitted.

Arima model: ARIMA(1,1,0)

ARIMA1_Chile<-Arima(COVID_series_Chile_Train,order=c(1,1,0),xreg=COVID_2_Day_Chile_Temp$Temperature_Chile[1:76])

summary(ARIMA1_Chile)
Series: COVID_series_Chile_Train 
Regression with ARIMA(1,1,0) errors 

Coefficients:
         ar1     xreg
      0.9734  -1.3786
s.e.  0.0265   1.2712

sigma^2 estimated as 1708:  log likelihood=-386
AIC=778.01   AICc=778.35   BIC=784.96

Training set error measures:
                   ME     RMSE      MAE MPE MAPE      MASE       ACF1
Training set 6.143615 40.50828 20.18216 NaN  Inf 0.3143638 -0.5175849

Forecast prediction to compare

Test_Chile_Temp<-COVID_2_Day_Chile_Temp$Temperature_Chile[77:length(COVID_Day_series_Chile_Temp)]


P_CHL_1<-forecast(ARIMA1_Chile,xreg = Test_Chile_Temp)

Calculate errors:

RE_CHL_1<-RE(P_CHL_1$mean,as.vector(COVID_series_Chile_Test))
MAPE_CHL_1<-MAPE(P_CHL_1$mean,as.vector(COVID_series_Chile_Test))
MSE_CHL_1<-MSE(P_CHL_1$mean,as.vector(COVID_series_Chile_Test))
PFA_CHL_1<-PFA(P_CHL_1$mean,as.vector(COVID_series_Chile_Test))

Errors.CHL_1<-c(RE_CHL_1,MAPE_CHL_1,MSE_CHL_1,PFA_CHL_1)
Errors.CHL_1
[1] 5.184414e-02 4.820284e+00 1.619636e+05 1.250000e-01

Model without temperature

ARIMA(1,2,4)

ARIMA2_Chile<-Arima(COVID_series_Chile_Train,order=c(1,2,4))
summary(ARIMA2_Chile)
Series: COVID_series_Chile_Train 
ARIMA(1,2,4) 

Coefficients:
         ar1      ma1     ma2     ma3      ma4
      0.8988  -1.8812  1.0871  0.0198  -0.0671
s.e.  0.0654   0.1487  0.2748  0.2611   0.1410

sigma^2 estimated as 843.5:  log likelihood=-354.47
AIC=720.93   AICc=722.19   BIC=734.76

Training set error measures:
                   ME     RMSE      MAE      MPE    MAPE     MASE        ACF1
Training set 2.726442 27.67343 12.61158 8.098567 15.1208 0.196442 -0.02684075

Forecast prediction to compare

P_CHL_2<-forecast(ARIMA2_Chile,h=length(COVID_series_Chile_Test))

Calculate errors:

RE_CHL_2<-RE(P_CHL_2$mean,as.vector(COVID_series_Chile_Test))
MAPE_CHL_2<-MAPE(P_CHL_2$mean,as.vector(COVID_series_Chile_Test))
MSE_CHL_2<-MSE(P_CHL_2$mean,as.vector(COVID_series_Chile_Test))
PFA_CHL_2<-PFA(P_CHL_2$mean,as.vector(COVID_series_Chile_Test))

Errors.CHL_2<-c(RE_CHL_2,MAPE_CHL_2,MSE_CHL_2,PFA_CHL_2)
Errors.CHL_2
[1] 2.967803e-02 2.799603e+00 5.188357e+04 1.250000e-01
Errors<-rbind(Errors.CHL_1,Errors.CHL_2)

rownames(Errors)<-c("ARIMAX(1,1,0)","ARIMA(1,2,4)")

colnames(Errors)<-c("Relative error","Mean Abs % error","Mean Squared Error","PFA")

Errors<-as.data.frame(Errors)

maximum<-apply(Errors,2,max)

minimum<-apply(Errors,2,min)

Errors<-rbind(minimum,Errors)

Errors<-rbind(maximum,Errors)
radarchart(Errors,maxmin=TRUE,axistype=4,axislabcol="slategray4",
           centerzero=FALSE,seg=8,cglcol="gray67",
           pcol=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"),
           plty=1,
           plwd=3,
           title="Chile forecast: error comparison on test set")

legend <-legend(1.5,1, legend=c("ARIMAX(1,1,0)","ARIMA(1,2,4)"),
                 seg.len=-1.4,
                 title="Errors",
                 pch=21, 
                 bty="n" ,lwd=3, y.intersp=1, horiz=FALSE,
                 col=c("dodgerblue2","firebrick2","darkorange2","darkorchid2"))

Conclusion: Keep model without temperature

Make model with the overall series

ARIMA_Final_Chile<-Arima(COVID_Day_series_Chile_Temp,order=c(0,2,1))
summary(ARIMA_Final_Chile)
Series: COVID_Day_series_Chile_Temp 
ARIMA(0,2,1) 

Coefficients:
          ma1
      -0.4633
s.e.   0.0934

sigma^2 estimated as 1978:  log likelihood=-427.15
AIC=858.3   AICc=858.46   BIC=863.12

Training set error measures:
                   ME    RMSE      MAE      MPE     MAPE      MASE        ACF1
Training set 8.103709 43.6722 20.75474 5.336459 15.41471 0.2175879 -0.02280619

Final forecast:

Future_Chile_Temp<-Chile_Temp$Temperature_Chile[Chile_Temp$Date>max(COVID_2$Date2)]

P_CHL_Final<-forecast(ARIMA_Final_Chile,h=length(Future_Chile_Temp))
Low_lim_CHL<-data.frame(P_CHL_Final$lower)[,2]
Upp_lim_CHL<-data.frame(P_CHL_Final$upper)[,2]

For making the plot:

##Data periods
per_1 <- as.Date(as.character(COVID_2_Day_Chile_Temp$Date2))
per_2 <- seq(as.Date(max(COVID_2_Day_Chile_Temp$Date2)+1,format="%Y-%m-%d"), as.Date("2020-04-30",format="%Y-%m-%d"),"1 day")


# Merge forecast + actuals
data <- xts(COVID_Day_series_Chile_Temp,order.by=per_1) 
dataNA <- rep(NA, length(data))
A <- cbind(data,dataNA,dataNA,dataNA)


Low_lim_CHL <- xts(Low_lim_CHL,order.by=per_2)
Forecast_CHL <- xts(P_CHL_Final$mean,order.by=per_2)
Upp_lim_CHL <- xts(Upp_lim_CHL,order.by=per_2)
predNA <- rep(NA, length(Forecast_CHL))
B <- cbind(predNA, Low_lim_CHL, Forecast_CHL, Upp_lim_CHL)

all_series_CHL <- data.frame(rbind(as.matrix(A),as.matrix(B)))
colnames(all_series_CHL) <- c('Actual', 'Lower_limit', 'Forecast', 'Upper_limit')
dygraph(all_series_CHL, main="SARS-COV2-outbreak: Total Chile cases",xlab="Date", ylab="Novel coronavirus cases",width = 750)%>%
  dySeries(c('Lower_limit', 'Forecast', 'Upper_limit'),label="Forecast",strokeWidth=2,
           drawPoints = TRUE, pointSize = 2, color=rgb(196/255,60/255,44/255)) %>%
  dySeries("Actual",drawPoints = TRUE, strokeWidth=2, pointSize = 2,
           color=rgb(16/255,59/255,160/255)) %>% 
  dyRangeSelector()

NA
LS0tCnRpdGxlOiAiQ09WSUQtMTkgT3V0YnJlYWs6IFdvcmxkd2lkZSBhbmFseXNpcyIKYXV0aG9yOiAiQW91biwgQ2FtYXJnbywgTWFydGluZXosIFJvZHJpZ3VleiIKZGF0ZTogIkFwcmlsIDE1dGgsIDIwMjAiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogdHJ1ZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICB0aGVtZTogY29zbW8KICAgICAKLS0tCiFbXShDb3JvbmF2aXJ1cy5qcGcpCgojIFF1aWNrIG92ZXJ2aWV3CgojIyBDdXJyZW50IHN0YXR1cwoKTG9hZCB0aGUgcmVxdWllcmVkIGxpYnJhcmllcyBmb3IgdGhlIGFuYWx5c2lzOgpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CiNsaWJyYXJ5KG5Db3YyMDE5KQpsaWJyYXJ5KGxlYWZsZXQpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShwbG90bHkpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KHh0cykKbGlicmFyeShkeWdyYXBocykKbGlicmFyeShjb3JycGxvdCkKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoZm1zYikKbGlicmFyeShmb3JlY2FzdCkKYGBgCgpMb2FkIHRoZSBnaXZlbiBkYXRhLCBhbmQgdGhlIG9uZSB1cGRhdGVkIGZyb20gWzIwMTkgTm92ZWwgQ29yb25hdmlydXMgQ09WSUQtMTkgKDIwMTktbkNvVikgRGF0YSBSZXBvc2l0b3J5IGJ5IEpvaG5zIEhvcGtpbnMgQ1NTRV0oaHR0cHM6Ly9naXRodWIuY29tL0NTU0VHSVNhbmREYXRhL0NPVklELTE5KQpgYGB7cn0KQ09WSUQ8LXJlYWQuY3N2KCJjb3ZpZF8xOV9kYXRhLmNzdiIpCkNPVklEXzI8LXJlYWQuY3N2KCJDT1ZJRDE5XzE0LUFwci5jc3YiKQpgYGAKCkZvcm1hdCBkYXRlOgpgYGB7cn0KRGF0ZTwtYXMuRGF0ZShDT1ZJRF8yJERhdGUsIGZvcm1hdD0iJW0vJWQvJXkiKSAKCkNPVklEXzIkRGF0ZTI8LURhdGUKYGBgCgpGaWx0ZXIgdG8gb2J0YWluIHRoZSBsYXN0IGRhdGU6CmBgYHtyfQpDT1ZJRF91cGRhdGVkPC1DT1ZJRF8yICU+JSBmaWx0ZXIoRGF0ZTI9PW1heChEYXRlMikpCmBgYAoKV29ybGQgbWFwIGNhc2VzOgpgYGB7cix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9CmxlYWZsZXQod2lkdGggPSAiMTAwJSIpICU+JSAKICBhZGRQcm92aWRlclRpbGVzKCJDYXJ0b0RCLkRhcmtNYXR0ZXIiKSAlPiUgCiAgc2V0VmlldyhsbmcgPSAwLCBsYXQgPSAxMCwgem9vbSA9IDEuNSkgJT4lIAogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IENPVklEX3VwZGF0ZWQsIAogICAgICAgICAgICAgICAgICAgbG5nID0gfiBMb25nLAogICAgICAgICAgICAgICAgICAgbGF0ID0gfiBMYXQsCiAgICAgICAgICAgICAgICAgICByYWRpdXMgPSB+IGxvZyhDb25maXJtZWQrMSksCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IHJnYigyMTgvMjU1LDY1LzI1NSw1Ni8yNTUpLAogICAgICAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSB+IGlmZWxzZShDb25maXJtZWQgPiAwLCAxLCAwKSwKICAgICAgICAgICAgICAgICAgIHN0cm9rZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgbGFiZWwgPSB+IHBhc3RlKFByb3ZpbmNlLlN0YXRlLCIsIixDb3VudHJ5LlJlZ2lvbiwgIjogIiwgQ29uZmlybWVkKQogICAgICAgICAgICAgICAgICAgKQpgYGAKCkN1cnJlbnQgdG9wIDEwIGNvdW50cmllcyBieSBjYXNlczoKYGBge3J9CkNPVklEX3RvcDwtQ09WSURfMiAlPiUgZmlsdGVyKERhdGUyPT1tYXgoRGF0ZTIpKSAlPiUgCiAgZ3JvdXBfYnkoQ291bnRyeS5SZWdpb24pICU+JSBzdW1tYXJpc2UoVG90YWxfY29uZmlybWVkPXN1bShDb25maXJtZWQpKSAlPiUgCiAgdG9wX24oMTAsVG90YWxfY29uZmlybWVkKSAlPiUgYXJyYW5nZShkZXNjKFRvdGFsX2NvbmZpcm1lZCkpCmBgYAoKYGBge3J9CnBsb3Q8LWdncGxvdChkYXRhPUNPVklEX3RvcAogICAgICAgLCBhZXMoeD1Ub3RhbF9jb25maXJtZWQseT1yZW9yZGVyKENvdW50cnkuUmVnaW9uLFRvdGFsX2NvbmZpcm1lZCkpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ImlkZW50aXR5IixhbHBoYT0wLjgsZmlsbD0iZmlyZWJyaWNrMyIpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPVRvdGFsX2NvbmZpcm1lZCksIHZqdXN0PTAuNSwgaGp1c3Q9MC45LGNvbG9yPSJibGFjayIsIHNpemU9My41KSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgbGFicyh0aXRsZSA9IHBhc3RlKCJUb3AgMTAgY291bnRyaWVzIHdpdGggY29uZmlybWVkIGNhc2VzIGFzIG9mICIsbWF4KENPVklEXzIkRGF0ZTIpKSwKICAgICAgIHggPSAiQ29uZmlybWVkIGNhc2VzIiwKICAgICAgIHkgPSAiQ291bnRyeSIpICsKICB0aGVtZV9taW5pbWFsKCkKCmdncGxvdGx5KHBsb3QsdG9vbHRpcCA9IGMoIngiKSx3aWR0aD03NTApCmBgYAoKVGltZSBkaXN0cmlidXRpb24gb2YgY2FzZXMsIGRlYXRocyBhbmQgcmVjb3ZlcmVkOgpgYGB7cn0KQ09WSURfMl9EYXk8LSBDT1ZJRF8yICU+JSBncm91cF9ieShEYXRlMikgJT4lIHN1bW1hcmlzZShXb3JsZF9jb25maXJtZWQ9c3VtKENvbmZpcm1lZCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV29ybGRfZGVhdGhzPXN1bShEZWF0aHMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdvcmxkX3JlY292ZXJlZD1zdW0oUmVjb3ZlcmVkKSkKCgpDT1ZJRF9EYXlfY29uZmlybWVkX3NlcmllczwteHRzKENPVklEXzJfRGF5JFdvcmxkX2NvbmZpcm1lZCwgb3JkZXIuYnk9Q09WSURfMl9EYXkkRGF0ZTIpCkNPVklEX0RheV9kZWF0aHNfc2VyaWVzPC14dHMoQ09WSURfMl9EYXkkV29ybGRfZGVhdGhzLCBvcmRlci5ieT1DT1ZJRF8yX0RheSREYXRlMikKQ09WSURfRGF5X3JlY292ZXJlZF9zZXJpZXM8LXh0cyhDT1ZJRF8yX0RheSRXb3JsZF9yZWNvdmVyZWQsIG9yZGVyLmJ5PUNPVklEXzJfRGF5JERhdGUyKQoKRGF5X3N1bW1hcnk8LWNiaW5kKENPVklEX0RheV9jb25maXJtZWRfc2VyaWVzLENPVklEX0RheV9kZWF0aHNfc2VyaWVzLENPVklEX0RheV9yZWNvdmVyZWRfc2VyaWVzKQpgYGAKCmBgYHtyfQpkeWdyYXBoKERheV9zdW1tYXJ5LCBtYWluID0gIlNBUlMtQ09WMi1vdXRicmVhazogVG90YWwgd29ybGR3aWRlIGNhc2VzIiwgCiAgICAgICAgeGxhYj0iRGF0ZSIsIHlsYWI9IlRvdGFsIGNhc2VzIix3aWR0aCA9IDc1MCkgJT4lIAogIGR5U2VyaWVzKCJDT1ZJRF9EYXlfY29uZmlybWVkX3NlcmllcyIsICJUb3RhbCBjYXNlcyIsZHJhd1BvaW50cyA9IFRSVUUsIAogICAgICAgICAgIHBvaW50U2l6ZSA9IDMsIGNvbG9yPXJnYig1My8yNTUsMTE2LzI1NSwxOTkvMjU1KSkgJT4lIAogIGR5U2VyaWVzKCJDT1ZJRF9EYXlfZGVhdGhzX3NlcmllcyIsICJUb3RhbCBkZWF0aHMiLGRyYXdQb2ludHMgPSBUUlVFLCAKICAgICAgICAgICBwb2ludFNpemUgPSAzLCBjb2xvcj1yZ2IoMTg5LzI1NSw1NS8yNTUsNDgvMjU1KSkgJT4lIAogIGR5U2VyaWVzKCJDT1ZJRF9EYXlfcmVjb3ZlcmVkX3NlcmllcyIsICJUb3RhbCByZWNvdmVyZWQiLGRyYXdQb2ludHMgPSBUUlVFLCAKICAgICAgICAgICBwb2ludFNpemUgPSAzLCBjb2xvcj1yZ2IoNjkvMjU1LDEzNi8yNTUsNTEvMjU1KSkgJT4lIAogIGR5UmFuZ2VTZWxlY3RvcigpCmBgYAoKQ3JlYXRlIGEgZnVuY3Rpb24gdG8gY2FsY3VsYXRlIG5ldyBjYXNlczoKYGBge3J9Ck5ld19jb3VudDwtZnVuY3Rpb24oeCkKewogIERhaWx5X2Nhc2VzPC1udW1lcmljKGxlbmd0aCh4KSkKICAKICBmb3IoaSBpbiBsZW5ndGgoeCk6MikKICB7CiAgICBEYWlseV9jYXNlc1tpXT14W2ldIC0geFtpLTFdCiAgfQogIHJldHVybihEYWlseV9jYXNlcykKfQoKTmV3X2Nhc2VzPC1OZXdfY291bnQoQ09WSURfMl9EYXkkV29ybGRfY29uZmlybWVkKQpOZXdfZGVhdGhzPC1OZXdfY291bnQoQ09WSURfMl9EYXkkV29ybGRfZGVhdGhzKQpOZXdfcmVjb3ZlcmVkPC1OZXdfY291bnQoQ09WSURfMl9EYXkkV29ybGRfcmVjb3ZlcmVkKQpDT1ZJRF9OZXdfY29uZmlybWVkX3NlcmllczwteHRzKE5ld19jYXNlcywgb3JkZXIuYnk9Q09WSURfMl9EYXkkRGF0ZTIpCkNPVklEX05ld19kZWF0aHNfc2VyaWVzPC14dHMoTmV3X2RlYXRocywgb3JkZXIuYnk9Q09WSURfMl9EYXkkRGF0ZTIpCkNPVklEX05ld19yZWNvdmVyZWRfc2VyaWVzPC14dHMoTmV3X3JlY292ZXJlZCwgb3JkZXIuYnk9Q09WSURfMl9EYXkkRGF0ZTIpCgpOZXdfc3VtbWFyeTwtY2JpbmQoQ09WSURfTmV3X2NvbmZpcm1lZF9zZXJpZXMsQ09WSURfTmV3X2RlYXRoc19zZXJpZXMsQ09WSURfTmV3X3JlY292ZXJlZF9zZXJpZXMpCmBgYAoKTmV3IGNhc2VzLCBkZWF0aHMgYW5kIHJlY292ZXJlZCB0aW1lIGRpc3RyaWJ1dGlvbjoKYGBge3J9CmR5Z3JhcGgoTmV3X3N1bW1hcnksIG1haW4gPSAiU0FSUy1DT1YyLW91dGJyZWFrOiBUb3RhbCB3b3JsZHdpZGUgY2FzZXMiLCAKICAgICAgICB4bGFiPSJEYXRlIiwgeWxhYj0iTm92ZWwgY29yb25hdmlydXMgY2FzZXMiLHdpZHRoID0gNzUwKSAlPiUgCiAgZHlTZXJpZXMoIkNPVklEX05ld19jb25maXJtZWRfc2VyaWVzIiwgIk5ldyBjYXNlcyIsZHJhd1BvaW50cyA9IFRSVUUsIAogICAgICAgICAgIHBvaW50U2l6ZSA9IDMsIGNvbG9yPXJnYig1My8yNTUsMTE2LzI1NSwxOTkvMjU1KSkgJT4lIAogIGR5U2VyaWVzKCJDT1ZJRF9OZXdfZGVhdGhzX3NlcmllcyIsICJOZXcgZGVhdGhzIixkcmF3UG9pbnRzID0gVFJVRSwgCiAgICAgICAgICAgcG9pbnRTaXplID0gMywgY29sb3I9cmdiKDE4OS8yNTUsNTUvMjU1LDQ4LzI1NSkpICU+JSAKICBkeVNlcmllcygiQ09WSURfTmV3X3JlY292ZXJlZF9zZXJpZXMiLCAiTmV3IHJlY292ZXJlZCIsZHJhd1BvaW50cyA9IFRSVUUsIAogICAgICAgICAgIHBvaW50U2l6ZSA9IDMsIGNvbG9yPXJnYig2OS8yNTUsMTM2LzI1NSw1MS8yNTUpKSAlPiUgCiAgZHlSYW5nZVNlbGVjdG9yKCkKYGBgCgpUb3RhbCBjYXNlcyBieSBzb21lIGNvdW50cmllcyB0aW1lIGRpc3RyaWJ1dGlvbjoKYGBge3J9CkNPVklEXzJfRGF5X0xlYmFub248LSBDT1ZJRF8yICU+JSAKICBmaWx0ZXIoQ291bnRyeS5SZWdpb24gJWluJSBjKCJMZWJhbm9uIikpICU+JSAKICBncm91cF9ieShEYXRlMikgJT4lIHN1bW1hcmlzZShXb3JsZF9jb25maXJtZWQ9c3VtKENvbmZpcm1lZCkpCgpDT1ZJRF8yX0RheV9DaGlsZTwtIENPVklEXzIgJT4lIAogIGZpbHRlcihDb3VudHJ5LlJlZ2lvbiAlaW4lIGMoIkNoaWxlIikpICU+JSAKICBncm91cF9ieShEYXRlMikgJT4lIHN1bW1hcmlzZShXb3JsZF9jb25maXJtZWQ9c3VtKENvbmZpcm1lZCkpCgpDT1ZJRF8yX0RheV9Db2xvbWJpYTwtIENPVklEXzIgJT4lIAogIGZpbHRlcihDb3VudHJ5LlJlZ2lvbiAlaW4lIGMoIkNvbG9tYmlhIikpICU+JSAKICBncm91cF9ieShEYXRlMikgJT4lIHN1bW1hcmlzZShXb3JsZF9jb25maXJtZWQ9c3VtKENvbmZpcm1lZCkpCgpDT1ZJRF8yX0RheV9Db3N0YVJpY2E8LSBDT1ZJRF8yICU+JSAKICBmaWx0ZXIoQ291bnRyeS5SZWdpb24gJWluJSBjKCJDb3N0YSBSaWNhIikpICU+JSAKICBncm91cF9ieShEYXRlMikgJT4lIHN1bW1hcmlzZShXb3JsZF9jb25maXJtZWQ9c3VtKENvbmZpcm1lZCkpCgoKQ09WSURfRGF5X3Nlcmllc19MZWJhbm9uPC14dHMoQ09WSURfMl9EYXlfTGViYW5vbiRXb3JsZF9jb25maXJtZWQsIG9yZGVyLmJ5PUNPVklEXzJfRGF5X0xlYmFub24kRGF0ZTIpCkNPVklEX0RheV9zZXJpZXNfQ2hpbGU8LXh0cyhDT1ZJRF8yX0RheV9DaGlsZSRXb3JsZF9jb25maXJtZWQsIG9yZGVyLmJ5PUNPVklEXzJfRGF5X0NoaWxlJERhdGUyKQpDT1ZJRF9EYXlfc2VyaWVzX0NvbG9tYmlhPC14dHMoQ09WSURfMl9EYXlfQ29sb21iaWEkV29ybGRfY29uZmlybWVkLCBvcmRlci5ieT1DT1ZJRF8yX0RheV9Db2xvbWJpYSREYXRlMikKQ09WSURfRGF5X3Nlcmllc19Db3N0YVJpY2E8LXh0cyhDT1ZJRF8yX0RheV9Db3N0YVJpY2EkV29ybGRfY29uZmlybWVkLCBvcmRlci5ieT1DT1ZJRF8yX0RheV9Db3N0YVJpY2EkRGF0ZTIpCgpPdXJfQ291bnRyaWVzPC1jYmluZChDT1ZJRF9EYXlfc2VyaWVzX0xlYmFub24sQ09WSURfRGF5X3Nlcmllc19DaGlsZSxDT1ZJRF9EYXlfc2VyaWVzX0NvbG9tYmlhLENPVklEX0RheV9zZXJpZXNfQ29zdGFSaWNhKQoKYGBgCgpgYGB7cn0KZHlncmFwaChPdXJfQ291bnRyaWVzLCBtYWluID0gIlNBUlMtQ09WMi1vdXRicmVhazogVG90YWwgY2FzZXMgYnkgY291bnRyeSIsIHhsYWI9IkRhdGUiLCB5bGFiPSJUb3RhbCBjYXNlcyIsd2lkdGggPSA3NTApICU+JSAKICBkeVNlcmllcygiQ09WSURfRGF5X3Nlcmllc19MZWJhbm9uIiwgIkxlYmFub24iLGRyYXdQb2ludHMgPSBUUlVFLCAKICAgICAgICAgICBwb2ludFNpemUgPSAzLCBjb2xvcj1yZ2IoMCwwLDMvMjU1KSkgJT4lIAogIGR5U2VyaWVzKCJDT1ZJRF9EYXlfc2VyaWVzX0NoaWxlIiwgIkNoaWxlIixkcmF3UG9pbnRzID0gVFJVRSwgCiAgICAgICAgICAgcG9pbnRTaXplID0gMyxjb2xvcj1yZ2IoMTIwLzI1NSwyOC8yNTUsMTA5LzI1NSkpICU+JSAKICBkeVNlcmllcygiQ09WSURfRGF5X3Nlcmllc19Db2xvbWJpYSIsICJDb2xvbWJpYSIsZHJhd1BvaW50cyA9IFRSVUUsIAogICAgICAgICAgIHBvaW50U2l6ZSA9IDMsY29sb3I9cmdiKDIzNy8yNTUsMTA1LzI1NSwzNy8yNTUpKSAlPiUgCiAgZHlTZXJpZXMoIkNPVklEX0RheV9zZXJpZXNfQ29zdGFSaWNhIiwgIkNvc3RhIFJpY2EiLGRyYXdQb2ludHMgPSBUUlVFLAogICAgICAgICAgIHBvaW50U2l6ZSA9IDMsY29sb3I9cmdiKDIwNC8yNTUsMTk3LzI1NSwxMjYvMjU1KSkgJT4lIAogIGR5UmFuZ2VTZWxlY3RvcigpCmBgYAoKTmV3IGNhc2VzIGJ5IHNvbWUgY291bnRyaWVzIHRpbWUgZGlzdHJpYnV0aW9uOgpgYGB7cn0KTmV3X0xlYmFub248LU5ld19jb3VudChDT1ZJRF8yX0RheV9MZWJhbm9uJFdvcmxkX2NvbmZpcm1lZCkKTmV3X0NoaWxlPC1OZXdfY291bnQoQ09WSURfMl9EYXlfQ2hpbGUkV29ybGRfY29uZmlybWVkKQpOZXdfQ29sb21iaWE8LU5ld19jb3VudChDT1ZJRF8yX0RheV9Db2xvbWJpYSRXb3JsZF9jb25maXJtZWQpCk5ld19Db3N0YVJpY2E8LU5ld19jb3VudChDT1ZJRF8yX0RheV9Db3N0YVJpY2EkV29ybGRfY29uZmlybWVkKQoKQ09WSURfTmV3X3Nlcmllc19MZWJhbm9uPC14dHMoTmV3X0xlYmFub24sIG9yZGVyLmJ5PUNPVklEXzJfRGF5X0xlYmFub24kRGF0ZTIpCkNPVklEX05ld19zZXJpZXNfQ2hpbGU8LXh0cyhOZXdfQ2hpbGUsIG9yZGVyLmJ5PUNPVklEXzJfRGF5X0NoaWxlJERhdGUyKQpDT1ZJRF9OZXdfc2VyaWVzX0NvbG9tYmlhPC14dHMoTmV3X0NvbG9tYmlhLCBvcmRlci5ieT1DT1ZJRF8yX0RheV9Db2xvbWJpYSREYXRlMikKQ09WSURfTmV3X3Nlcmllc19Db3N0YVJpY2E8LXh0cyhOZXdfQ29zdGFSaWNhLCBvcmRlci5ieT1DT1ZJRF8yX0RheV9Db3N0YVJpY2EkRGF0ZTIpCgpPdXJfTmV3X0NvdW50cmllczwtY2JpbmQoQ09WSURfTmV3X3Nlcmllc19MZWJhbm9uLENPVklEX05ld19zZXJpZXNfQ2hpbGUsQ09WSURfTmV3X3Nlcmllc19Db2xvbWJpYSxDT1ZJRF9OZXdfc2VyaWVzX0Nvc3RhUmljYSkKYGBgCgpgYGB7cn0KZHlncmFwaChPdXJfTmV3X0NvdW50cmllcywgbWFpbiA9ICJTQVJTLUNPVjItb3V0YnJlYWs6IE5ldyBjYXNlcyBieSBjb3VudHJ5IiwgeGxhYj0iRGF0ZSIsIHlsYWI9IlRvdGFsIGNhc2VzIix3aWR0aCA9IDc1MCkgJT4lIAogIGR5U2VyaWVzKCJDT1ZJRF9OZXdfc2VyaWVzX0xlYmFub24iLCAiTGViYW5vbiIsZHJhd1BvaW50cyA9IFRSVUUsIAogICAgICAgICAgIHBvaW50U2l6ZSA9IDMsIGNvbG9yPXJnYigwLDAsMy8yNTUpKSAlPiUgCiAgZHlTZXJpZXMoIkNPVklEX05ld19zZXJpZXNfQ2hpbGUiLCAiQ2hpbGUiLGRyYXdQb2ludHMgPSBUUlVFLCAKICAgICAgICAgICBwb2ludFNpemUgPSAzLGNvbG9yPXJnYigxMjAvMjU1LDI4LzI1NSwxMDkvMjU1KSkgJT4lIAogIGR5U2VyaWVzKCJDT1ZJRF9OZXdfc2VyaWVzX0NvbG9tYmlhIiwgIkNvbG9tYmlhIixkcmF3UG9pbnRzID0gVFJVRSwgCiAgICAgICAgICAgcG9pbnRTaXplID0gMyxjb2xvcj1yZ2IoMjM3LzI1NSwxMDUvMjU1LDM3LzI1NSkpICU+JSAKICBkeVNlcmllcygiQ09WSURfTmV3X3Nlcmllc19Db3N0YVJpY2EiLCAiQ29zdGEgUmljYSIsZHJhd1BvaW50cyA9IFRSVUUsCiAgICAgICAgICAgcG9pbnRTaXplID0gMyxjb2xvcj1yZ2IoMjA0LzI1NSwxOTcvMjU1LDEyNi8yNTUpKSAlPiUgCiAgZHlSYW5nZVNlbGVjdG9yKCkKYGBgCgojIExvb2tpbmcgZm9yIGNvcnJlbGF0aW9ucwoKM0Qgc2NhdHRlcnBsb3Qgb2YgY29uZmlybWVkIGNhc2VzIFZzLiBEZWF0aHMgVnMuIFJlY292ZXJlZCwgdXNpbmcgdGhlIGRhdGEgZm9yIHRoZSBsYXN0IGRheQpgYGB7cn0KZmlnIDwtIHBsb3RfbHkoQ09WSURfdXBkYXRlZCwgeCA9IH5Db25maXJtZWQsIHkgPSB+RGVhdGhzLCB6ID0gflJlY292ZXJlZCwgd2lkdGg9NzUwKSAlPiUgCiAgYWRkX21hcmtlcnModGV4dD0gfkNvdW50cnkuUmVnaW9uICxob3ZlcmluZm89ICJ0ZXh0IiwKICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yPXJnYigxODkvMjU1LDU1LzI1NSw0OC8yNTUpKSkgJT4lIAogIGxheW91dCh0aXRsZT0iQ29uZmlybWVkIGNhc2VzIFZzLiBEZWF0aHMgVnMuIFJlY292ZXJlZCIsIHNjZW5lID0gbGlzdCgKICAgICAgICAgICAgICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAnQ29uZmlybWVkJyksCiAgICAgICAgICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICdEZWF0aHMnKSwKICAgICAgICAgICAgICAgICAgICAgemF4aXMgPSBsaXN0KHRpdGxlID0gJ1JlY292ZXJlZCcpKSkgCmZpZwpgYGAKIyMgRm9yIHRoZSBudW1iZXIgb2YgY2FzZXMKCkluIHRoaXMgc3RlcCwgd2Ugd2lsbCBsb29rIGZvciBjb3JyZWxhdGlvbnMgb2YgdGhlIG51bWJlciBvZiBjYXNlcyBvZiBDT1ZJRC0xOSwgYnkgc29tZSBrbm93biBpbmRpY2F0b3JzLiBIZXJlICoqZWFjaCBpbmRpdmlkdWFsIG9mIGFuYWx5c2lzICh0dXBsZSkgaXMgYSBjb3VudHJ5KiouCgoKIyMjIEh1bWFuIERldmVsb3BtZW50IEluZGV4CgpUaGUgSHVtYW4gRGV2ZWxvcG1lbnQgSW5kZXggaXMgY2FsY3VsYXRlZCBieSB0aGUgVW5pdGVkIE5hdGlvbnMuIEFuZCBpdHMgbG9jYXRlZCBoZXJlOiBbVW5pdGVkIE5hdGlvbnMgSHVtYW4gRGV2ZWxvcG1lbnQgcmVwb3J0c10oaHR0cDovL2hkci51bmRwLm9yZy9lbi9kYXRhKQpgYGB7cn0KSERJPC1yZWFkLmNzdigiSHVtYW4gRGV2ZWxvcG1lbnQgSW5kZXggKEhESSlfMi5jc3YiLHNlcD0iOyIsZGVjPSIsIikKYGBgCgpHcm91cCBkYXRhIGJ5IGNvdW50cnk6CmBgYHtyfQpDT1ZJRF9Db3VudHJ5PC1DT1ZJRF8yICU+JSBmaWx0ZXIoRGF0ZTI9PW1heChEYXRlMikpICU+JSAKICBncm91cF9ieShDb3VudHJ5LlJlZ2lvbikgJT4lIHN1bW1hcmlzZShUb3RhbF9jb25maXJtZWQ9c3VtKENvbmZpcm1lZCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVG90YWxfZGVhdGhzPXN1bShEZWF0aHMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRvdGFsX1JlY292ZXJlZD1zdW0oUmVjb3ZlcmVkKSkKYGBgCgpSZW1vdmUgYWZ0ZXIgcGFyZW50aGVzZXM6IFVzZSByZWd1bGFyIGV4cHJlc2lvbnMgdG8gY2xlYW4gdGhlIGNvdW50cmllcyBuYW1lIG9uIHRoZSBIREkgZmlsZSwgdGhpcyB0byB1c2UgdGhpcyB2YXJpYWJsZSBhcyBrZXkgdG8gam9pbiB3aXRoIHRoZSBDT1ZJRC0xOSBjb3VudHJpZXMgdGFibGU6CmBgYHtyfQpIREkkQ291bnRyeV8yPC1nc3ViKCJcXHMqXFwoW15cXCldK1xcKSIsIiIsYXMuY2hhcmFjdGVyKEhESSRDb3VudHJ5KSkKYGBgCgpgYGB7cn0KSERJJENvdW50cnlfMltIREkkQ291bnRyeV8yPT0iVW5pdGVkIFN0YXRlcyJdPC0iVVMiCkhESSRDb3VudHJ5XzJbSERJJENvdW50cnlfMj09IktvcmVhIl08LSJTb3V0aCBLb3JlYSIKYGBgCgpCZWNhdXNlIHRoZSBudW1iZXIgb2YgY2FzZXMgYnkgY291bnRyeSBpcyBjbGVhcmx5IGFmZmVjdGVkIGJ5IHRoZSBwb3B1bGF0aW9uIChmb3IgZXhhbXBsZSB0aGUgbnVtYmVyIG9mIGNhc2VzIGluIExlYmFub24gaXMgd2F5IHNtYWxsZXIgdGhhbiB0aGUgVVMsIGJ1dCBMZWJhbm9uIGhhcyA2TSBpbmhhYml0YW50cyBhbmQgdGhlIFVTIDMzME0pLCB0aGlzIHZhcmlhYmxlIHdhcyB1c2VkIHRvIG9idGFpbiB0aGUgbnVtYmVyIG9mIGNhc2VzIHBlciBtaWxsaW9uIGluaGFiaXRhbnRzLgoKVGhlIGRhdGEgdXNlZCBpcyBmcm9tIHRoZSBbV29ybGQgQmFuayBpbmRpY2F0b3JzXShodHRwczovL2RhdGEud29ybGRiYW5rLm9yZy9pbmRpY2F0b3Ivc3AucG9wLnRvdGw/ZW5kPTIwMTgmc3RhcnQ9MjAxOCkKYGBge3J9ClBvcHVsYXRpb248LXJlYWQuY3N2KCJXb3JsZF9wb3B1bGF0aW9uLmNzdiIsc2VwPSI7IixkZWM9IiwiKQpgYGAKClJlbW92ZSBhZnRlciBjb21tbWE6IFVzZSByZWd1bGFyIGV4cHJlc2lvbnMgdG8gY2xlYW4gdGhlIGNvdW50cmllcyBuYW1lIG9uIHRoZSBwb3B1bGF0aW9uIGZpbGUsIHRoaXMgdG8gdXNlIHRoaXMgdmFyaWFibGUgYXMga2V5IHRvIGpvaW4gd2l0aCB0aGUgQ09WSUQtMTkgY291bnRyaWVzIHRhYmxlOgpgYGB7cn0KUG9wdWxhdGlvbiRDb3VudHJ5X05hbWVfMjwtZ3N1YigiLC4qIiwgIiIsIGFzLmNoYXJhY3RlcihQb3B1bGF0aW9uJENvdW50cnlfTmFtZSkpCmBgYAoKYGBge3J9ClBvcHVsYXRpb24kQ291bnRyeV9OYW1lXzJbUG9wdWxhdGlvbiRDb3VudHJ5X05hbWVfMj09IlVuaXRlZCBTdGF0ZXMiXTwtIlVTIgpQb3B1bGF0aW9uJENvdW50cnlfTmFtZV8yW1BvcHVsYXRpb24kQ291bnRyeV9Db2RlPT0iS09SIl08LSJTb3V0aCBLb3JlYSIKUG9wdWxhdGlvbiRDb3VudHJ5X05hbWVfMltQb3B1bGF0aW9uJENvdW50cnlfQ29kZT09IkNaRSJdPC0iQ3plY2hpYSIKYGBgCgpOYXR1cmFsIEpvaW46CmBgYHtyLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0KQ09WSURfMzwtIENPVklEX0NvdW50cnkgJT4lIGlubmVyX2pvaW4oSERJLGJ5PWMoIkNvdW50cnkuUmVnaW9uIj0iQ291bnRyeV8yIikpICU+JSAKICBpbm5lcl9qb2luKFBvcHVsYXRpb24sYnk9YygiQ291bnRyeS5SZWdpb24iPSJDb3VudHJ5X05hbWVfMiIpKSAlPiUgCiAgc2VsZWN0KENvdW50cnkuUmVnaW9uLFRvdGFsX2NvbmZpcm1lZCxUb3RhbF9kZWF0aHMsVG90YWxfUmVjb3ZlcmVkLEhESV9SYW5rXzIwMTgsWWVhcl8yMDE4LAogICAgICAgICBDb3VudHJ5X0NvZGUsUG9wdWxhdGlvbl8yMDE4KSAlPiUgCiAgbXV0YXRlKENhc2VzX21pbGxpb249KFRvdGFsX2NvbmZpcm1lZC9Qb3B1bGF0aW9uXzIwMTgpKjEwMDAwMDAsCiAgICAgICAgIFJlY292ZXJlZF9wZXJjZW50YWdlPShUb3RhbF9SZWNvdmVyZWQvVG90YWxfY29uZmlybWVkKSoxMDApICAKCkNPVklEXzM8LUNPVklEXzNbIWlzLm5hKENPVklEXzMkUG9wdWxhdGlvbl8yMDE4KSxdCgpgYGAKCkNvdW50cmllcyB3aXRoIHRvcCAxMCBjYXNlcyBwZXIgbWlsbGlvbiBpbmhhYml0YW50cwpgYGB7cn0KQ09WSUQzX3RvcDwtQ09WSURfMyAlPiUgdG9wX24oMTAsQ2FzZXNfbWlsbGlvbikgJT4lIGFycmFuZ2UoZGVzYyhDYXNlc19taWxsaW9uKSkgJT4lIAogIG11dGF0ZShDYXNlc19taWxsaW9uPXJvdW5kKENhc2VzX21pbGxpb24sMCkpCmBgYAoKYGBge3J9CnBsb3Q8LWdncGxvdChkYXRhPUNPVklEM190b3AKICAgICAgICwgYWVzKHg9Q2FzZXNfbWlsbGlvbix5PXJlb3JkZXIoQ291bnRyeS5SZWdpb24sQ2FzZXNfbWlsbGlvbikpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ImlkZW50aXR5IixhbHBoYT0wLjgsZmlsbD0ic2t5IGJsdWUiLCApICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPUNhc2VzX21pbGxpb24pLCB2anVzdD0wLjUsIGhqdXN0PTAuOSxjb2xvcj0iYmxhY2siLCBzaXplPTMuNSkgKwogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiVG9wIDEwIGNvdW50cmllcyB3aXRoIGNvbmZpcm1lZCBjYXNlcyBwZXIgbWlsbGlvbiBoYWJpdGFudCBcbiBhcyBvZiAiLG1heChDT1ZJRF8yJERhdGUyKSksCiAgICAgICB4ID0gIkNvbmZpcm1lZCBjYXNlcyBwZXIgbWlsbGlvbiBoYWJpdGFudHMiLAogICAgICAgeSA9ICJDb3VudHJ5IikgKwogIHRoZW1lX21pbmltYWwoKQoKZ2dwbG90bHkocGxvdCx0b29sdGlwID0gYygieCIpLHdpZHRoPTc1MCkKYGBgCgoKUGxvdCB0aGUgSHVtYW4gRGV2ZWxvcG1lbnQgSW5kZXgoSERJKSBWcy4gdGhlIG51bWJlciBvZiBjYXNlcyAoYXBwbHlpbmcgYSBsb2cgdHJhbnNmb3JtYXRpb24sIHRvIGF2b2lkIGVmZmVjdHMgb2Ygb3V0bGllcnMpLCBhbmQgdGhlIHByb3BvcnRpb24gb2YgcmVjb3ZlcmVkIGNhc2VzOgpgYGB7cn0KcGxvdDwtZ2dwbG90KGRhdGE9Q09WSURfMyxhZXMoeD1sb2coQ2FzZXNfbWlsbGlvbikseT1ZZWFyXzIwMTgsCiAgICAgICAgICAgICAgICAgICAgICAgIHNpemU9UmVjb3ZlcmVkX3BlcmNlbnRhZ2UsdGV4dD1Db3VudHJ5LlJlZ2lvbikpICsKICBnZW9tX3BvaW50KGNvbG9yPSJibGFjayIsZmlsbD1yZ2IoMjM3LzI1NSwxMDUvMjU1LDM3LzI1NSksc2hhcGU9MjEsYWxwaGE9MC42KSArCiAgc2NhbGVfc2l6ZShyYW5nZSA9IGMoMywxNSksIG5hbWU9IlJlY292ZXJlZCBcbiBwZXJjZW50YWdlIikgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIikgKwogIGxhYnModGl0bGU9IkhESSBWcy4gbG9nYXJpdGhtdXMgb2YgQ09WSUQtMTkgY2FzZXMgYnkgbWlsbGlvbiBpbmhhYml0YW50cyBcbiBhbmQgcHJvcG9ydGlvbiBvZiByZWNvdmVyZWQiLAogICAgICAgeD0ibG4oQ2FzZXMvMU0gcG9wdWxhdGlvbikiLAogICAgICAgeT0iSERJIikKCmdncGxvdGx5KHBsb3QsdG9vbHRpcCA9IGMoInRleHQiKSx3aWR0aD03NTApCmBgYAoKQ29ycmVsYXRpb24gbWF0cml4OiAKYGBge3J9CkNPVklEX251bWVyaWNfMTwtQ09WSURfMyAlPiUgbXV0YXRlKExvZ19jYXNlcz1sb2coQ2FzZXNfbWlsbGlvbiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlYXRoX3BlcmNlbnRhZ2U9KFRvdGFsX2RlYXRocy9Ub3RhbF9jb25maXJtZWQpKjEwMCkgJT4lIAogIHNlbGVjdChMb2dfY2FzZXMsUmVjb3ZlcmVkX3BlcmNlbnRhZ2UsRGVhdGhfcGVyY2VudGFnZSxZZWFyXzIwMTgpCgpjb3JycGxvdChjb3IoQ09WSURfbnVtZXJpY18xKSxtZXRob2QgPSAibnVtYmVyIix0bC5jb2w9ImJsYWNrIix0bC5zcnQ9MTUsCiAgICAgICAgIGNvbD1jb2xvclJhbXBQYWxldHRlKGMocmdiKDIwNC8yNTUsMTk3LzI1NSwxMjYvMjU1KSxyZ2IoMjM3LzI1NSwxMDUvMjU1LDM3LzI1NSkpKSgyMDApKQpgYGAKCmBgYHtyfQpNZWFzbGVzPC1yZWFkLmNzdigiTWVhc2xlc19pbW11bml6YXRpb24uY3N2IixzZXA9IjsiLGRlYz0iLiIpCmBgYAoKYGBge3J9Ck1lYXNsZXMkQ291bnRyeV8yPC1nc3ViKCJcXHMqXFwoW15cXCldK1xcKSIsIiIsYXMuY2hhcmFjdGVyKE1lYXNsZXMkQ291bnRyeSkpCmBgYAoKYGBge3J9Ck1lYXNsZXMkQ291bnRyeV8yW01lYXNsZXMkQ291bnRyeV8yPT0iVW5pdGVkIFN0YXRlcyJdPC0iVVMiCk1lYXNsZXMkQ291bnRyeV8yW01lYXNsZXMkQ291bnRyeV8yPT0iS29yZWEiXTwtIlNvdXRoIEtvcmVhIgpgYGAKCmBgYHtyfQpDT1ZJRF8zPC0gQ09WSURfMyAlPiUgaW5uZXJfam9pbihNZWFzbGVzLGJ5PWMoIkNvdW50cnkuUmVnaW9uIj0iQ291bnRyeV8yIikpICU+JSBzZWxlY3QoLWMoIkNvdW50cnkiKSkKYGBgCgoKIyMjIEhlYWx0aCBleHBlbmRpdHVyZSAoJSBvZiBHRFApCgpUaGUgbmV4dCB2YXJpYWJsZSB0byBjb25zaWRlciBpcyB0aGUgcGVyY2VudGFnZSBvZiB0aGUgR0RQIHRoYXQgdGhlIGNvdW50cmllcyBzcGVuZCBvbiBoZWFsdGgsIHdoaWNoIGlzIGFsc28gYXZhaWxhYmxlIG9uIFtVbml0ZWQgTmF0aW9ucyBIdW1hbiBEZXZlbG9wbWVudCByZXBvcnRzXShodHRwOi8vaGRyLnVuZHAub3JnL2VuL2RhdGEpIApgYGB7cn0KSGVhbHRoX2V4cGVuZGl0dXJlPC1yZWFkLmNzdigiSGVhbHRoX2V4cGVuZGl0dXJlX0dEUC5jc3YiLHNlcD0iOyIsZGVjPSIuIikKYGBgCgpSZW1vdmUgYWZ0ZXIgcGFyZW50aGVzZXM6IFVzZSByZWd1bGFyIGV4cHJlc2lvbnMgdG8gY2xlYW4gdGhlIGNvdW50cmllcyBuYW1lIG9uIHRoZSBoZWFsdGggZXhwZW5kaXR1cmUgZmlsZSwgdGhpcyB0byB1c2UgdGhpcyB2YXJpYWJsZSBhcyBrZXkgdG8gam9pbiB3aXRoIHRoZSBDT1ZJRC0xOSBjb3VudHJpZXMgdGFibGU6CmBgYHtyfQpIZWFsdGhfZXhwZW5kaXR1cmUkQ291bnRyeV8yPC1nc3ViKCJcXHMqXFwoW15cXCldK1xcKSIsIiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMuY2hhcmFjdGVyKEhlYWx0aF9leHBlbmRpdHVyZSRDb3VudHJ5KSkKYGBgCgpgYGB7cn0KSGVhbHRoX2V4cGVuZGl0dXJlJENvdW50cnlfMltIZWFsdGhfZXhwZW5kaXR1cmUkQ291bnRyeV8yPT0iVW5pdGVkIFN0YXRlcyJdPC0iVVMiCkhlYWx0aF9leHBlbmRpdHVyZSRDb3VudHJ5XzJbSGVhbHRoX2V4cGVuZGl0dXJlJENvdW50cnlfMj09IktvcmVhIl08LSJTb3V0aCBLb3JlYSIKYGBgCgpOYXR1cmFsIGpvaW4gb2YgdGFibGVzOgpgYGB7cn0KQ09WSURfMzwtIENPVklEXzMgJT4lIGlubmVyX2pvaW4oSGVhbHRoX2V4cGVuZGl0dXJlLGJ5PWMoIkNvdW50cnkuUmVnaW9uIj0iQ291bnRyeV8yIikpICU+JSBzZWxlY3QoLWMoIkNvdW50cnkiKSkKYGBgCgpQbG90IHRoZSAlIG9mIEdEUCBzcGVudCBvbiBoZWFsdGggVnMgdGhlIENhc2VzLzFNIHBvcHVsYXRpb246CmBgYHtyfQpwbG90PC1nZ3Bsb3QoZGF0YT1DT1ZJRF8zLGFlcyh4PWxvZyhDYXNlc19taWxsaW9uKSx5PUV4cGVuZGl0dXJlXzIwMTYsCiAgICAgICAgICAgICAgICAgICAgICAgIHNpemU9UmVjb3ZlcmVkX3BlcmNlbnRhZ2UsdGV4dD1Db3VudHJ5LlJlZ2lvbikpICsKICBnZW9tX3BvaW50KGNvbG9yPSJibGFjayIsZmlsbD1yZ2IoMjA0LzI1NSwxOTcvMjU1LDEyNi8yNTUpLHNoYXBlPTIxLGFscGhhPTAuNikgKwogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDMsMTUpLCBuYW1lPSJSZWNvdmVyZWQgXG4gcGVyY2VudGFnZSIpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpICsKICBsYWJzKHRpdGxlPSJIZWFsdGggZXhwZW5kaXR1cmUgKCUgb2YgR0RQKSBWcy4gbG9nYXJpdGhtdXMgb2YgQ09WSUQtMTkgY2FzZXMgYnkgbWlsbGlvbiBpbmhhYml0YW50cyBcbiBhbmQgcHJvcG9ydGlvbiBvZiByZWNvdmVyZWQiLAogICAgICAgeD0ibG4oQ2FzZXMvMU0gcG9wdWxhdGlvbikiLAogICAgICAgeT0iJSBvZiBHRFAgaW4gaGVhbHRoIikKCmdncGxvdGx5KHBsb3QsdG9vbHRpcCA9IGMoInRleHQiKSx3aWR0aD03NTApCmBgYAoKIyMjIFRlbXBlcmF0dXJlCgpPdGhlciB2YXJpYWJsZSB0byBjb25zaWRlcmVkIGlzIHRoZSB0ZW1wZXJhdHVyZSBvZiB0aGUgY291bnRyaWVzLiBXaGlsZSB0aGUgaXMgbm8gZW1waXJpY2FsIGV2aWRlbmNlIHRoYXQgdGhlIHdlYXRoZXIgYWZmZWN0cyBvciBoZWxwcyB0aGUgc3ByZWFkIG9mIHRoZSBTQVJTLUNPViAyIHZpcnVzLiBUaGlzIFtwYXBlciBjb3ZlcnMgaG93IGdlbmVyYWxseSwgdGhlIHdlYXRoZXIgaGFkIGFmZmVjdGVkIHRoZSBzcHJlYWQgb2YgdmlyYWwgcmVzcGlyYXRvcnkgaW5mZWN0aW9uc10oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DMzMxMTk4OC8pCgpUaGlzIGRhdGEgY29udGFpbnMgdGhlIHRlbXBlcmF0dXJlIChpbiBjZWxjaXVzKSBvZiB0aGUgZmlyc3QgZGF5IG9mIHRoZSBtb250aCwgZm9yIGFsbCBjb3VudHJpZXMgc2luY2UgMTkwMCwgYW5kIGl0IGlzIGF2YWlsYWJsZSBvbiB0aGlzIFtLYWdnbGVdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vYmVya2VsZXllYXJ0aC9jbGltYXRlLWNoYW5nZS1lYXJ0aC1zdXJmYWNlLXRlbXBlcmF0dXJlLWRhdGEpCmBgYHtyfQpUZW1wZXJhdHVyZTwtcmVhZC5jc3YoIkdsb2JhbExhbmRUZW1wZXJhdHVyZXNCeUNvdW50cnkuY3N2IixzZXA9IjsiKQpgYGAKCkZvcm1hdCBkYXRlOgpgYGB7cn0KRGF0ZV9UZW1wPC1hcy5EYXRlKFRlbXBlcmF0dXJlJGR0LCBmb3JtYXQ9IiVkLyVtLyVZIikgCgpUZW1wZXJhdHVyZSREYXRlX1RlbXA8LURhdGVfVGVtcApgYGAKCkV4dHJhY3QgbW9udGggYW5kIGZpbHRlciBmb3IgSVEgKEphbiwgRmViIGFuZCBNYXIpLCBiZWNhdXNlIHRoYXQgYXJlIHRoZSBtb250aHMgb3VyIENPVklEIGRhdGEgaXMgYXZhaWxhYmxlLCBhbmQgb2J0YWluIHRoZSBhdmVyYWdlIElRIHRlbXBlcmF0dXJlOgpgYGB7cn0KVGVtcGVyYXR1cmU8LSBUZW1wZXJhdHVyZSAlPiUgbXV0YXRlKE1vbnRoPW1vbnRoKFRlbXBlcmF0dXJlJERhdGVfVGVtcCxsYWJlbCA9VFJVRSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBZZWFyPXllYXIoVGVtcGVyYXR1cmUkRGF0ZV9UZW1wKSkgJT4lIAogIGZpbHRlcihNb250aCAlaW4lIGMoIkphbiIsIkZlYiIsIk1hciIpICYgWWVhcj49MjAwMCkgICU+JSAKICBncm91cF9ieShDb3VudHJ5KSAlPiUgc3VtbWFyaXNlKEF2Z19JUV90ZW1wZXJhdHVyZT1tZWFuKEF2ZXJhZ2VUZW1wZXJhdHVyZSkpCgpUZW1wZXJhdHVyZTwtVGVtcGVyYXR1cmVbIWlzLm5hKFRlbXBlcmF0dXJlJEF2Z19JUV90ZW1wZXJhdHVyZSksXQpgYGAKClJlZ3VsYXIgZXhwcmVzc2lvbnMgdG8gY2xlYW4gdGhlIGNvdW50cmllcyBuYW1lOgpgYGB7cn0KVGVtcGVyYXR1cmUkQ291bnRyeV8yPC1nc3ViKCJcXHMqXFwoW15cXCldK1xcKSIsIiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMuY2hhcmFjdGVyKFRlbXBlcmF0dXJlJENvdW50cnkpKQpgYGAKCmBgYHtyfQpUZW1wZXJhdHVyZSRDb3VudHJ5XzJbVGVtcGVyYXR1cmUkQ291bnRyeV8yPT0iVW5pdGVkIFN0YXRlcyJdPC0iVVMiClRlbXBlcmF0dXJlJENvdW50cnlfMltUZW1wZXJhdHVyZSRDb3VudHJ5XzI9PSJLb3JlYSJdPC0iU291dGggS29yZWEiCmBgYAoKSm9pbiB0YWJsZXM6CmBgYHtyfQpDT1ZJRF8zPC0gQ09WSURfMyAlPiUgaW5uZXJfam9pbihUZW1wZXJhdHVyZSxieT1jKCJDb3VudHJ5LlJlZ2lvbiI9IkNvdW50cnlfMiIpKSAlPiUgc2VsZWN0KC1jKCJDb3VudHJ5IikpCgojd3JpdGUuY3N2KENPVklEXzMsIkNPVklEX0NvdmFyaWFibGVzLmNzdiIpCmBgYAoKUGxvdCB0aGUgYXZlcmFnZSB0ZW1wZXJhdHVyZSBvZiB0aGUgZmlyc3QgcXVhcnRlciBWcy4gdGhlIGNhc2VzLzFNIGhhYml0YW50czoKYGBge3J9CnBsb3Q8LWdncGxvdChkYXRhPUNPVklEXzMsYWVzKHg9bG9nKENhc2VzX21pbGxpb24pLHk9QXZnX0lRX3RlbXBlcmF0dXJlLAogICAgICAgICAgICAgICAgICAgICAgICBzaXplPVJlY292ZXJlZF9wZXJjZW50YWdlLHRleHQ9Q291bnRyeS5SZWdpb24pKSArCiAgZ2VvbV9wb2ludChjb2xvcj0iYmxhY2siLGZpbGw9cmdiKDEyMC8yNTUsMjgvMjU1LDEwOS8yNTUpLHNoYXBlPTIxLGFscGhhPTAuNikgKwogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDMsMTUpLCBuYW1lPSJSZWNvdmVyZWQgXG4gcGVyY2VudGFnZSIpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpICsKICBsYWJzKHRpdGxlPSJBdmVyYWdlIElRIHRlbXBlcmF0dXJlIFZzLiBsb2dhcml0aG11cyBvZiBDT1ZJRC0xOSBjYXNlcyBieSBtaWxsaW9uIGluaGFiaXRhbnRzIFxuIGFuZCBwcm9wb3J0aW9uIG9mIHJlY292ZXJlZCIsCiAgICAgICB4PSJsbihDYXNlcy8xTSBwb3B1bGF0aW9uKSIsCiAgICAgICB5PSJUZW1wZXJhdHVyZSAoQ2VsY2l1cykiKQoKZ2dwbG90bHkocGxvdCx0b29sdGlwID0gYygidGV4dCIpLHdpZHRoPTc1MCkKYGBgCgojIyBGb3IgdGhlIG51bWJlciBvZiBkZWF0aHMKClRoaXMgc3RlcCBpcyBzaW1pbGFyIHRvIHRoZSBwcmV2aW91cyBvbmUsIHdoZXJlICoqZWFjaCBpbmRpdmlkdWFsIG9mIGFuYWx5c2lzICh0dXBsZSkgaXMgYSBjb3VudHJ5KiosIGJ1dCBoZXJlIHdlIGFyZSBjb25zaWRlcmluZyB0aGUgKipkZWF0aCByYXRlIHBlcmNlbnRhZ2UuKioKCiMjIyBEVFAgaW1tdW5pemF0aW9uCgpUaGUgRFRQIGlzIGEgY2xhc3Mgb2YgY29tYmluYXRpb24gdmFjY2luZXMgYWdhaW5zdCB0aHJlZSBpbmZlY3Rpb3VzIGRpc2Vhc2VzIGluIGh1bWFuczogZGlwaHRoZXJpYSwgcGVydHVzc2lzICh3aG9vcGluZyBjb3VnaCksIGFuZCB0ZXRhbnVzLCB0aGUgZGF0YSBpcyBhbHNvIGF2YWlsYWJsZSBvbiB0aGUgW1VuaXRlZCBOYXRpb25zIEh1bWFuIERldmVsb3BtZW50IHJlcG9ydHNdKGh0dHA6Ly9oZHIudW5kcC5vcmcvZW4vZGF0YSkKYGBge3J9CkRUUF9pbW11bml6YXRpb248LXJlYWQuY3N2KCJJbmZhbnRzX2xhY2tpbmdfaW1tdW5pemF0aW9uX0RUUC5jc3YiLHNlcD0iOyIpCmBgYAoKUmVtb3ZlIGFmdGVyIHBhcmVudGhlc2VzOiByZWd1bGFyIGV4cHJlc3Npb24gY2xlYW5pbmcKYGBge3J9CkRUUF9pbW11bml6YXRpb24kQ291bnRyeV8yPC1nc3ViKCJcXHMqXFwoW15cXCldK1xcKSIsIiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmNoYXJhY3RlcihEVFBfaW1tdW5pemF0aW9uJENvdW50cnkpKQpgYGAKCgpgYGB7cn0KRFRQX2ltbXVuaXphdGlvbiRDb3VudHJ5XzJbRFRQX2ltbXVuaXphdGlvbiRDb3VudHJ5XzI9PSJVbml0ZWQgU3RhdGVzIl08LSJVUyIKRFRQX2ltbXVuaXphdGlvbiRDb3VudHJ5XzJbRFRQX2ltbXVuaXphdGlvbiRDb3VudHJ5XzI9PSJLb3JlYSJdPC0iU291dGggS29yZWEiCmBgYAoKSm9pbiB0YWJsZXM6CmBgYHtyLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0KQ09WSURfNDwtIENPVklEX0NvdW50cnkgJT4lIGlubmVyX2pvaW4oRFRQX2ltbXVuaXphdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnk9YygiQ291bnRyeS5SZWdpb24iPSJDb3VudHJ5XzIiKSkgJT4lIAogIHNlbGVjdChDb3VudHJ5LlJlZ2lvbixUb3RhbF9jb25maXJtZWQsVG90YWxfZGVhdGhzLFRvdGFsX1JlY292ZXJlZCwKICAgICAgICAgTGFja19EVFBfaW5tbXVuaXphdGlvbl8yMDE4KSAlPiUgCiAgbXV0YXRlKFJlY292ZXJlZF9wZXJjZW50YWdlPShUb3RhbF9SZWNvdmVyZWQvVG90YWxfY29uZmlybWVkKSoxMDAsCiAgICAgICAgIERlYXRoX3JhdGU9KFRvdGFsX2RlYXRocy9Ub3RhbF9jb25maXJtZWQpKjEwMCkKYGBgCgpQbG90IHRoZSBkZWF0aCByYXRlIFZzLiAlIG9mIGluZmFudHMgbGFja2luZyBEVFAgaW1tdW5pemF0aW9uOgpgYGB7cn0KcGxvdDwtZ2dwbG90KGRhdGE9Q09WSURfNCxhZXMoeD1EZWF0aF9yYXRlLHk9TGFja19EVFBfaW5tbXVuaXphdGlvbl8yMDE4LAogICAgICAgICAgICAgICAgICAgICAgICBzaXplPVJlY292ZXJlZF9wZXJjZW50YWdlLHRleHQ9Q291bnRyeS5SZWdpb24pKSArCiAgZ2VvbV9wb2ludChjb2xvcj0iYmxhY2siLGZpbGw9cmdiKDEyMC8yNTUsMjgvMjU1LDEwOS8yNTUpLHNoYXBlPTIxLGFscGhhPTAuNikgKwogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDMsMTUpLCBuYW1lPSJSZWNvdmVyZWQgXG4gcGVyY2VudGFnZSIpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpICsKICBsYWJzKHRpdGxlPSIlIGluZmFudHMgbGFja2luZyBEVFAgaW1tdW5pemF0aW9uIFZzLiBkZWF0aCByYXRlIGFuZCBcbiBwcm9wb3J0aW9uIG9mIHJlY292ZXJlZCIsCiAgICAgICB4PSJOb3ZlbCBjb3JvbmF2aXJ1cyBkZWF0aCByYXRlIiwKICAgICAgIHk9IiUgb2YgaW5mYW50cyIpCgpnZ3Bsb3RseShwbG90LHRvb2x0aXAgPSBjKCJ0ZXh0Iiksd2lkdGg9NzUwKQpgYGAKCiMjIyBJbmZhbnRzIGxhY2tpbmcgaW1tdW5pemF0aW9uLCBtZWFzbGVzICglIG9mIG9uZS15ZWFyLW9sZHMpCgpUaGUgbmV4dCB2YWNjaW5lIHRoYXQgd2FzIGV2YWx1YXRlZCB3YXMgbWVhc2xlcywgdGhlIGRhdGEgaXMgYWxzbyBhdmFpbGFibGUgb24gdGhlIFtVbml0ZWQgTmF0aW9ucyBIdW1hbiBEZXZlbG9wbWVudCByZXBvcnRzXShodHRwOi8vaGRyLnVuZHAub3JnL2VuL2RhdGEpCgpgYGB7cn0KQ09WSURfNDwtIENPVklEXzQgJT4lIGlubmVyX2pvaW4oTWVhc2xlcyxieT1jKCJDb3VudHJ5LlJlZ2lvbiI9IkNvdW50cnlfMiIpKSAlPiUgc2VsZWN0KC1jKCJDb3VudHJ5IikpCmBgYAoKU2VlIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlICUgb2QgaW5mYW50cyBsYWNraW5nIG1lYXNsZXMgdmFjY2luZSwgdGhlIG1lZGlhbiBvZiB0aGlzIHZhcmlhYmxlIGlzIGJlbG93IDEwJSwgd2l0aCBzb21lIG91dGxpZXJzIG92ZXIgNDAlIGluIHNvbWUgY291bnRyaWVzCmBgYHtyfQpnZ3Bsb3QoQ09WSURfNCwgYWVzKHk9TWVhc2xlc18yMDE4KSkgKyAKICBnZW9tX2JveHBsb3QoZmlsbD0iZG9kZ2VyYmx1ZTQiLG91dGxpZXIuc2hhcGUgPSAyMSwgCiAgICAgICAgICAgICAgIG91dGxpZXIuZmlsbCA9ICJmaXJlYnJpY2siLGFscGhhPTAuNzUpICsKICBnZ3RpdGxlKCJCb3hwbG90IG9mICUgaW5mYW50cyBsYWNraW5nIG1lYXNsZXMgaW1tdW5pemF0aW9uIikgKyB5bGFiKCIlIG9mIGluZmFudHMiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKSGlzdG9ncmFtOgpgYGB7cn0KZ2dwbG90KENPVklEXzQsIGFlcyhNZWFzbGVzXzIwMTgpKSArIAogIGdlb21faGlzdG9ncmFtKGZpbGw9ImRvZGdlcmJsdWU0IixiaW5zPTIwLGFscGhhPTAuOCkgKwogIGdndGl0bGUoIkhpc3RvZ3JhbSBvZiAlIGluZmFudHMgbGFja2luZyBtZWFzbGVzIGltbXVuaXphdGlvbiIpICsgCiAgeGxhYigiJSBvZiBpbmZhbnRzIikgKyAKICB5bGFiKCJDb3VudCIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpQbG90ICUgb2YgaW5mYW50cyBsYWNraW5nIHRoaXMgaW1tdW5pemF0aW9uIHZzIHRoZSBmYXRhbGl0eSByYXRlOgpgYGB7cn0KcGxvdDwtZ2dwbG90KGRhdGE9Q09WSURfNCxhZXMoeD1EZWF0aF9yYXRlLHk9TWVhc2xlc18yMDE4LAogICAgICAgICAgICAgICAgICAgICAgICBzaXplPVJlY292ZXJlZF9wZXJjZW50YWdlLHRleHQ9Q291bnRyeS5SZWdpb24pKSArCiAgZ2VvbV9wb2ludChjb2xvcj0iYmxhY2siLGZpbGw9cmdiKDIzNy8yNTUsMTA1LzI1NSwzNy8yNTUpLHNoYXBlPTIxLGFscGhhPTAuNikgKwogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDMsMTUpLCBuYW1lPSJSZWNvdmVyZWQgXG4gcGVyY2VudGFnZSIpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpICsKICBsYWJzKHRpdGxlPSIlIGluZmFudHMgbGFja2luZyBtZWFzbGVzIGltbXVuaXphdGlvbiBWcy4gZmF0YWxpdHkgcmF0ZSBcbiBhbmQgcHJvcG9ydGlvbiBvZiByZWNvdmVyZWQiLAogICAgICAgeD0iRmF0YWxpdHkgcmF0ZSAoJSkiLAogICAgICAgeT0iJSBvZiBpbmZhbnRzIikKCmdncGxvdGx5KHBsb3QsdG9vbHRpcCA9IGMoInRleHQiKSx3aWR0aD03NTApCmBgYAoKCiMgRml0dGluZyBhIHJlZ3Jlc3Npb24gbW9kZWwKCkhlcmUgdGhlICoqaW5kaXZpZHVhbHMgb2YgYW5hbHlzaXMgKHR1cGxlcykgd2lsbCBiZSBlYWNoIGNvdW50cnkqKiwgdGhlICoqcmVzcG9uc2UgdmFyaWFibGUgd2lsbCBiZSB0aGUgY2FzZXMgcGVyIG1pbGxpb24gaW5oYWJpdGFudHMqKiwgYW5kIHRoZSBleHBsYW5hdG9yeSB2YXJpYWJsZXMgc2hvd2VkIGJlZm9yZS4gCgpCZWNhdXNlIG9mIHRoZSBwcmVzZW5jZSBvZiBleHRyZW1lIGNhc2VzIHBlciBtaWxsaW9uIGluaGFiaXRhbnRzIGluIHNvbWUgY291bnRyaWVzLCBpdCBpcyBjb252ZW5pZW50IHRvIG1ha2UgYSBsbiB0cmFuc2Zvcm1hdGlvbiwgYW5kIGJlY2F1c2Ugc29tZSB0ZW1wZXJhdHVyZXMgYXJlIGJlbG93IDAgY2VsY2l1cywgdGhleSB3aWxsIGJlIGNvbnZlcnRlZCB0byBrZWx2aW4gKGFkZGluZyAyNzMuNSkgdG8gdGhlIGNlbGNpdXMgdGVtcGVyYXR1cmUuCgpGaXJzdCBtb2RlbDoKYGBge3J9Ck1vZDE8LWxtKGxvZyhDT1ZJRF8zJENhc2VzX21pbGxpb24pfmxvZyhDT1ZJRF8zJFllYXJfMjAxOCkrbG9nKENPVklEXzMkTWVhc2xlc18yMDE4KSsKICAgICAgICAgICBsb2coQ09WSURfMyRFeHBlbmRpdHVyZV8yMDE2KStsb2coQ09WSURfMyRBdmdfSVFfdGVtcGVyYXR1cmUrMjczLjE1KSkKc3VtbWFyeShNb2QxKQpgYGAKClN0ZXB3aXNlIHdpdGggQUlDIGNyaXRlcmlvbiwgdG8gc2VlIHdoaWNoIHZhcmlhYmxlcyBhcmUgYmV0dGVyOgpgYGB7cn0KTW9kMjwtc3RlcChNb2QxLGRpcmVjdGlvbiA9ICJib3RoIikKYGBgCgpGaW5hbCBtb2RlbDoKYGBge3J9CnN1bW1hcnkoTW9kMikKYGBgCgpBY2NvcmRpbmcgdG8gdGhlIEFJQyBjcml0ZXJpb24sIGEgbW9kZWwgd2l0aCBIREksICUgc3BlbnQgb24gaGVhbHRoIGFuZCBhdmVyYWdlIHRlbXBlcmF0dXJlIGlzIGJldHRlcjoKCk5vcm1hbGl0eSBvZiByZXNpZHVhbHM6CmBgYHtyfQpNb2RlbF9yZXNpZHVhbHM8LWRhdGEuZnJhbWUoUmVzaWR1YWxzPU1vZDIkcmVzaWR1YWxzKQpnZ3Bsb3QoTW9kZWxfcmVzaWR1YWxzLCBhZXMoUmVzaWR1YWxzKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShmaWxsPSJkb2RnZXJibHVlNCIsYmlucz0xOCxhbHBoYT0wLjgpICsKICBnZ3RpdGxlKCJIaXN0b2dyYW0gb2YgbW9kZWwgcmVzaWR1YWxzIikgKyAKICB4bGFiKCJSZXNpZHVhbHMiKSArIAogIHlsYWIoIkZyZXF1ZW5jeSIpICsKICB0aGVtZV9taW5pbWFsKCkKCnNoYXBpcm8udGVzdChNb2QyJHJlc2lkdWFscykKYGBgCgpCeSBhbmFseXNpbmcgdGhlIGhpc3RvZ3JhbSwgYW5kIHVzaW5nIHRoZSBTaGFwaXJvLVdpbGsgdGVzdCwgdGhlcmUgaXMgbm90IGVub3VnaCBzdGF0aXN0aWNhbCBldmlkZW5jZSB0byByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcyB0aGF0IHRoZSByZXNpZHVhbHMgb2YgdGhlIG1vZGVsIGFyZSBmb2xsb3dpbmcgYSBub3JtYWwgZGlzdHJpYnV0aW9uLiBOb3JtYWwgZGlzdHJpYnV0aW9uIGlzIGFzc3VtZWQuCgojIyBQcmVkaWN0aW9uIHBvd2VyCgpFdmFsdWF0ZSBpZiBhIG1vZGVsIHdpdGggdGVtcGVyYXR1cmUgVnMuIGEgb25lIHdpdGhvdXQgdGVtcGVyYXR1cmUuCgpTZXBhcmF0ZSBiZXR3ZWVuIHRyYWluaW5nIGFuZCB0ZXN0aW5nOgpgYGB7cn0Kc2V0LnNlZWQoMTc5ODE5KQpTYW1wbGUgPC0gc2FtcGxlKDE6bGVuZ3RoKENPVklEXzMkQ2FzZXNfbWlsbGlvbiksbGVuZ3RoKENPVklEXzMkQ2FzZXNfbWlsbGlvbikqMC4yMCkKdC50ZXN0aW5nPC0gQ09WSURfM1tTYW1wbGUsXQp0LnRyYWluaW5nPC0gQ09WSURfM1stU2FtcGxlLF0KYGBgCgpUcmFuc2Zvcm0gdGhlIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHZhcmlhYmxlcyBhcyBiZWZvcmU6CmBgYHtyfQp0LnRyYWluaW5nPC10LnRyYWluaW5nICU+JSBtdXRhdGUoQ2FzZXNfbWlsbGlvbl9sb2c9bG9nKENhc2VzX21pbGxpb24pLEhESV9sb2c9bG9nKFllYXJfMjAxOCksCiAgICAgICAgICAgICAgICAgICAgICBHRFBfbG9nPWxvZyhFeHBlbmRpdHVyZV8yMDE2KSwKICAgICAgICAgICAgICAgICAgICAgIFRlbXBlcmF0dXJlX2xvZ19rZWx2aW49bG9nKEF2Z19JUV90ZW1wZXJhdHVyZSsyNzMuMTUpKSAKCnQudHJhaW5pbmc8LXQudHJhaW5pbmdbLDE0OjE3XQoKdC50ZXN0aW5nPC10LnRlc3RpbmcgJT4lIG11dGF0ZShDYXNlc19taWxsaW9uX2xvZz1sb2coQ2FzZXNfbWlsbGlvbiksSERJX2xvZz1sb2coWWVhcl8yMDE4KSwKICAgICAgICAgICAgICAgICAgICAgIEdEUF9sb2c9bG9nKEV4cGVuZGl0dXJlXzIwMTYpLAogICAgICAgICAgICAgICAgICAgICAgVGVtcGVyYXR1cmVfbG9nX2tlbHZpbj1sb2coQXZnX0lRX3RlbXBlcmF0dXJlKzI3My4xNSkpIAoKdC50ZXN0aW5nPC10LnRlc3RpbmdbLDE0OjE3XQpgYGAKCkZpdCB0aGUgc2FtZSBtb2RlbCB3aXRoIHRyYWluaW5nCmBgYHtyfQpNb2QzPC1sbShDYXNlc19taWxsaW9uX2xvZ34uLCBkYXRhPXQudHJhaW5pbmcpCnN1bW1hcnkoTW9kMykKYGBgCgpFcnJvciBmdW5jdGlvbnM6CmBgYHtyfQojIFJlc2lkdWFsIFN1bSBvZiBTcXVhcmUgKFJTUykKUlNTPC1mdW5jdGlvbihQcmVkLEFjdHVhbCkgewogIHNzPC1zdW0oKEFjdHVhbC1QcmVkKV4yKQogIHJldHVybihzcykKfQoKIyBSZXNpZHVhbCBTdGFuZGFyZCBFcnJvciAoUlNFKQpSU0U8LWZ1bmN0aW9uKFByZWQsUmVhbCxOdW1QcmVkKSB7CiAgTjwtbGVuZ3RoKFJlYWwpLU51bVByZWQtMSAgCiAgc3M8LXNxcnQoKDEvTikqUlNTKFByZWQsUmVhbCkpCiAgcmV0dXJuKHNzKQp9CiMgTWVhbiBTcXVhcmVkIEVycm9yIApNU0UgPC0gZnVuY3Rpb24oUHJlZCxBY3R1YWwpIHsKICBOPC1sZW5ndGgoQWN0dWFsKQogIHNzPC0oMS9OKSpSU1MoUHJlZCxBY3R1YWwpCiAgcmV0dXJuKHNzKQp9CgojIFJlbGF0aXZlIGVycm9yClJlbGF0aXZlRXJyb3I8LWZ1bmN0aW9uKFByZWQsQWN0dWFsKSB7CiAgc3M8LXN1bShhYnMoQWN0dWFsLVByZWQpKS9zdW0oYWJzKEFjdHVhbCkpCiAgcmV0dXJuKHNzKQp9CmBgYAoKUHJlZGljdGlvbjoKYGBge3J9ClByZWQ8LXByZWRpY3QoTW9kMyx0LnRlc3RpbmcpCmBgYAoKRXJyb3JzOgpgYGB7cn0KUlNTX01vZDM8LVJTUyhQcmVkLHQudGVzdGluZyRDYXNlc19taWxsaW9uX2xvZykKUlNFX01vZDM8LVJTRShQcmVkLHQudGVzdGluZyRDYXNlc19taWxsaW9uX2xvZyxkaW0odC50ZXN0aW5nKVsyXS0xKQpNU0VfTW9kMzwtTVNFKFByZWQsdC50ZXN0aW5nJENhc2VzX21pbGxpb25fbG9nKQpSZWxhdGl2ZUVycm9yX01vZDM8LVJlbGF0aXZlRXJyb3IoUHJlZCx0LnRlc3RpbmckQ2FzZXNfbWlsbGlvbl9sb2cpCgpNb2QzX0Vycm9yczwtYyhSU1NfTW9kMyxSU0VfTW9kMyxNU0VfTW9kMyxSZWxhdGl2ZUVycm9yX01vZDMpCmBgYAoKTm93LCBhICoqbW9kZWwgd2l0aG91dCB0ZW1wZXJhdHVyZToqKgpgYGB7cn0KdC50cmFpbmluZyA8LSB0LnRyYWluaW5nICU+JSBzZWxlY3QoLVRlbXBlcmF0dXJlX2xvZ19rZWx2aW4pCnQudGVzdGluZyA8LSB0LnRlc3RpbmcgJT4lIHNlbGVjdCgtVGVtcGVyYXR1cmVfbG9nX2tlbHZpbikKYGBgCgpgYGB7cn0KTW9kNDwtbG0oQ2FzZXNfbWlsbGlvbl9sb2d+LiwgZGF0YT10LnRyYWluaW5nKQpzdW1tYXJ5KE1vZDQpCmBgYAoKUHJlZGljdGlvbjoKYGBge3J9ClByZWQ8LXByZWRpY3QoTW9kNCx0LnRlc3RpbmcpCmBgYAoKRXJyb3JzOgpgYGB7cn0KUlNTX01vZDQ8LVJTUyhQcmVkLHQudGVzdGluZyRDYXNlc19taWxsaW9uX2xvZykKUlNFX01vZDQ8LVJTRShQcmVkLHQudGVzdGluZyRDYXNlc19taWxsaW9uX2xvZyxkaW0odC50ZXN0aW5nKVsyXS0xKQpNU0VfTW9kNDwtTVNFKFByZWQsdC50ZXN0aW5nJENhc2VzX21pbGxpb25fbG9nKQpSZWxhdGl2ZUVycm9yX01vZDQ8LVJlbGF0aXZlRXJyb3IoUHJlZCx0LnRlc3RpbmckQ2FzZXNfbWlsbGlvbl9sb2cpCgpNb2Q0X0Vycm9yczwtYyhSU1NfTW9kNCxSU0VfTW9kNCxNU0VfTW9kNCxSZWxhdGl2ZUVycm9yX01vZDQpCmBgYAoKQ3JlYXRlIGEgcmFkYXJwbG90IHRvIGVhc2lseSBjb21wYXJlIGVycm9yczoKYGBge3J9CkVycm9yczwtcmJpbmQoTW9kM19FcnJvcnMsTW9kNF9FcnJvcnMpCgpyb3duYW1lcyhFcnJvcnMpPC1jKCJNb2RlbCB3aXRoIHRlbXBlcmF0dXJlIiwiTW9kZWwgd2l0aG91dCB0ZW1wZXJhdHVyZSIpCgpjb2xuYW1lcyhFcnJvcnMpPC1jKCJSZXNpZHVhbCBTdW0gb2YgU3F1YXJlIiwiUmVzaWR1YWwgU3RhbmRhcmQgRXJyb3IiLCJNZWFuIFNxdWFyZWQgRXJyb3IiLCJSZWxhdGl2ZSBlcnJvciIpCgpFcnJvcnM8LWFzLmRhdGEuZnJhbWUoRXJyb3JzKQoKbWF4aW11bTwtYXBwbHkoRXJyb3JzLDIsbWF4KQoKbWluaW11bTwtYXBwbHkoRXJyb3JzLDIsbWluKQoKRXJyb3JzPC1yYmluZChtaW5pbXVtLEVycm9ycykKCkVycm9yczwtcmJpbmQobWF4aW11bSxFcnJvcnMpCmBgYAoKYGBge3J9CnJhZGFyY2hhcnQoRXJyb3JzLG1heG1pbj1UUlVFLGF4aXN0eXBlPTQsYXhpc2xhYmNvbD0ic2xhdGVncmF5NCIsCiAgICAgICAgICAgY2VudGVyemVybz1GQUxTRSxzZWc9OCxjZ2xjb2w9ImdyYXk2NyIsCiAgICAgICAgICAgcGNvbD1jKCJkb2RnZXJibHVlMiIsImZpcmVicmljazIiLCJkYXJrb3JhbmdlMiIsImRhcmtvcmNoaWQyIiksCiAgICAgICAgICAgcGx0eT0xLAogICAgICAgICAgIHBsd2Q9MywKICAgICAgICAgICB0aXRsZT0iRXJyb3IgY29tcGFyaXNvbiIpCgpsZWdlbmQgPC1sZWdlbmQoMS41LDEsIGxlZ2VuZD1jKCJXaXRoIHRlbXBlcmF0dXJlIiwiV2l0aG91dCB0ZW1wZXJhdHVyZSIpLAogICAgICAgICAgICAgICAgIHNlZy5sZW49LTEuNCwKICAgICAgICAgICAgICAgICB0aXRsZT0iRXJyb3JzIiwKICAgICAgICAgICAgICAgICBwY2g9MjEsIAogICAgICAgICAgICAgICAgIGJ0eT0ibiIgLGx3ZD0zLCB5LmludGVyc3A9MSwgaG9yaXo9RkFMU0UsCiAgICAgICAgICAgICAgICAgY29sPWMoImRvZGdlcmJsdWUyIiwiZmlyZWJyaWNrMiIsImRhcmtvcmFuZ2UyIiwiZGFya29yY2hpZDIiKSkKYGBgCgpUaGVyZWZvcmUsIGEgbW9kZWwgd2l0aG91dCB0ZW1wZXJhdHVyZSBpcyBiZXR0ZXIuICoqQnV0IGJlY2F1c2UgdGVtcGVyYXR1cmUgd2FzIGluY2x1ZGVkIHVzaW5nIHRoZSBzdGVwd2lzZSBBSUMgY3JpdGVyaW9uLCBhbmQgYmVjYXVzZSBpdHMgY29lZmZpY2llbnQgd2FzIHNpZ25pZmljYW50IChhdCBhbiBhbHBoYSBvZiAxMCUpLCBpdCB3aWxsIGJlIGNvbnNpZGVyZWQgZm9yIHRoZSBkYXktdG8tZGF5IGZvcmVjYXN0KioKCgojIEZvcmVjYXN0IGJ5IGNvdW50cnkKCkZvciBlYWNoIG9mIHRoZSBmb2xsb3dpbmcgY291bnRyaWVzLCB0aGUgYmVzdCBzdWl0YWJsZSBBUklNQSBtb2RlbCB3aWxsIGJlIGZpdHRlZCwgKiphbmQgaXRzIHByZWRpY3Rpb24gcG93ZXIgd2lsbCBiZSBjb21wYXJlZCAoYSBtb2RlbCB3aXRoIHRlbXBlcmF0dXJlIGFuZCBhIG1vZGVsIHdpdGhvdXQpKioKClRoZSB3ZWF0aGVyIGNvbnNpZGVyZWQgZm9yIHRoZXNlIG1vZGVscyB3aWxsIGJlIHRoZSBtYXhpbXVtIHByZXNlbnRlZCBhbmQgZm9yZWNhc3RlZCBpbiBbQWNjdVdlYXRoZXJdKGh0dHBzOi8vd3d3LmFjY3V3ZWF0aGVyLmNvbSkKCiMjIFJlcHVibGljIG9mIENvc3RhIFJpY2EKCiFbXShDb3N0YVJpY2FGbGFnLnBuZyl7d2lkdGg9NDAlfQoKKk1vZGVsIHdpdGggdGVtcGVyYXR1cmUqCgpgYGB7cn0KQ29zdGFSaWNhX1RlbXA8LXJlYWQuY3N2KCJDb3N0YVJpY2FfVGVtcGVyYXR1cmUuY3N2IixzZXA9IjsiKQpDb3N0YVJpY2FfVGVtcCREYXRlPC1hcy5EYXRlKENvc3RhUmljYV9UZW1wJERhdGUsZm9ybWF0PSIlZC8lbS8lWSIpCmBgYAoKYGBge3J9CkNPVklEXzJfRGF5X0Nvc3RhUmljYTwtIENPVklEXzIgJT4lIAogIGZpbHRlcihDb3VudHJ5LlJlZ2lvbiAlaW4lIGMoIkNvc3RhIFJpY2EiKSkgJT4lIAogIGdyb3VwX2J5KERhdGUyKSAlPiUgc3VtbWFyaXNlKFdvcmxkX2NvbmZpcm1lZD1zdW0oQ29uZmlybWVkKSkKCkNPVklEXzJfRGF5X0Nvc3RhUmljYV9UZW1wPC1DT1ZJRF8yX0RheV9Db3N0YVJpY2EgJT4lCiAgaW5uZXJfam9pbihDb3N0YVJpY2FfVGVtcCxieT1jKCJEYXRlMiI9IkRhdGUiKSkKCkNPVklEX0RheV9zZXJpZXNfQ29zdGFSaWNhX1RlbXA8LXh0cyhDT1ZJRF8yX0RheV9Db3N0YVJpY2FfVGVtcCRXb3JsZF9jb25maXJtZWQsIG9yZGVyLmJ5PUNPVklEXzJfRGF5X0Nvc3RhUmljYV9UZW1wJERhdGUyKQpgYGAKCmBgYHtyfQpDT1ZJRF9zZXJpZXNfQ29zdGFSaWNhX1RlbXBfVHJhaW48LUNPVklEX0RheV9zZXJpZXNfQ29zdGFSaWNhX1RlbXBbMTo2NF0gI1VudGlsIE1hcmNoIDI1dGgKQ09WSURfc2VyaWVzX0Nvc3RhUmljYV9UZW1wX1Rlc3Q8LUNPVklEX0RheV9zZXJpZXNfQ29zdGFSaWNhX1RlbXBbNjU6bGVuZ3RoKENPVklEX0RheV9zZXJpZXNfQ29zdGFSaWNhX1RlbXApXSAjRnJvbSBNYXJjaCAyNnRoIG9ud2FyZHMKYGBgCgpHZXQgdGhlIGRpZmZlcmVudGlhdGVkIHNlcmllczogSW4gdGhpcyBjYXNlIHVudGlsIGEgc2Vjb25kIGRpZmZlcmVuY2Ugd2FzIHN1aXRhYmxlCmBgYHtyfQpwbG90KGRpZmYoZGlmZihDT1ZJRF9zZXJpZXNfQ29zdGFSaWNhX1RlbXBfVHJhaW4pKSx0eXBlPSJsIixtYWluPSJDb3N0YSBSaWNhOiAybmQgZGlmZmVyZW5jZSIpIApgYGAKCkNvcnJlbG9ncmFtcyBvZiBzZWNvbmQgZGlmZXJlbmNlOgpgYGB7cn0KdHNkaXNwbGF5KGRpZmYoZGlmZihDT1ZJRF9zZXJpZXNfQ29zdGFSaWNhX1RlbXBfVHJhaW4pKSwgbWFpbj0iQ29zdGEgUmljYSBBQ0YgYW5kIFBBQ0YgY29ycmVsb2dyYW1zIikKYGBgCgpCeSBhbmFseXppbmcgdGhlIGF1dG9jb3JyZWxhdGlvbiBhbmQgcGFydGlhbCBhdXRvY29ycmVsYXRpb24gZnVuY3Rpb24gcGxvdHMuIEEgbW9kZWwgd2l0aCAxIGF1dG9yZWdyZXNzaXZlIHRlcm0sIGFuZCBvbmUgbW92aW5nIGF2ZXJhZ2UgdGVybSB3aWxsIGJlIGZpdHRlZApBcmltYSBtb2RlbDogQVJJTUEoMSwyLDEpCmBgYHtyfQpBUklNQTFfQ29zdGFSaWNhPC1BcmltYShDT1ZJRF9zZXJpZXNfQ29zdGFSaWNhX1RlbXBfVHJhaW4sb3JkZXI9YygxLDIsMSkseHJlZz1DT1ZJRF8yX0RheV9Db3N0YVJpY2FfVGVtcCRUZW1wZXJhdHVyZV9Db3N0YVJpY2FbMTo2NF0pCgpzdW1tYXJ5KEFSSU1BMV9Db3N0YVJpY2EpCmBgYAoKUHJlZGljdGl2ZSBlcnJvciBmdW5jdGlvbnM6CmBgYHtyfQojUmVsYXRpdmUgZXJyb3IKUkUgPC0gZnVuY3Rpb24oRm9yZSxBY3R1YWwpIHsKICByZXR1cm4oc3VtKGFicyhGb3JlLUFjdHVhbCkpL2FicyhzdW0oQWN0dWFsKSkpCn0KCgojTUFQRQpNQVBFPC1mdW5jdGlvbihGb3JlLEFjdHVhbCl7CiAgcmV0dXJuKAogICAgbWVhbihhYnMoQWN0dWFsLUZvcmUpL2FicyhBY3R1YWwpKSoxMDAKICAgICkKfQoKIyBtZWFuIHNxdWFyZWQgZXJyb3IgKE1TRSkKTVNFPC1mdW5jdGlvbihGb3JlLEFjdHVhbCkgewogIE48LWxlbmd0aChBY3R1YWwpCiAgc3M8LXN1bSgoQWN0dWFsLUZvcmUpXjIpCiAgcmV0dXJuKCgxL04pKnNzKQp9CgojUEZBClBGQSA8LSBmdW5jdGlvbihGb3JlLEFjdHVhbCkgewogIFRvdGFsPC0wCiAgTjwtbGVuZ3RoKEZvcmUpCiAgZm9yKGkgaW4gMTpOKSB7CiAgICBpZihGb3JlW2ldPj1BY3R1YWxbaV0pCiAgICAgIFRvdGFsPC1Ub3RhbCsxICAgICAgCiAgfQogIHJldHVybihUb3RhbC9OKQp9CmBgYAoKRm9yZWNhc3QgcHJlZGljdGlvbiB0byBjb21wYXJlOgpgYGB7cn0KVGVzdF9Db3N0YVJpY2FfVGVtcDwtQ09WSURfMl9EYXlfQ29zdGFSaWNhX1RlbXAkVGVtcGVyYXR1cmVfQ29zdGFSaWNhWzY1Omxlbmd0aChDT1ZJRF9EYXlfc2VyaWVzX0Nvc3RhUmljYV9UZW1wKV0KCgpQX0NSSV8xPC1mb3JlY2FzdChBUklNQTFfQ29zdGFSaWNhLHhyZWcgPSBUZXN0X0Nvc3RhUmljYV9UZW1wKQpgYGAKCkNhbGN1bGF0ZSBlcnJvcnM6CmBgYHtyfQpSRV9DUklfMTwtUkUoUF9DUklfMSRtZWFuLGFzLnZlY3RvcihDT1ZJRF9zZXJpZXNfQ29zdGFSaWNhX1RlbXBfVGVzdCkpCk1BUEVfQ1JJXzE8LU1BUEUoUF9DUklfMSRtZWFuLGFzLnZlY3RvcihDT1ZJRF9zZXJpZXNfQ29zdGFSaWNhX1RlbXBfVGVzdCkpCk1TRV9DUklfMTwtTVNFKFBfQ1JJXzEkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX0Nvc3RhUmljYV9UZW1wX1Rlc3QpKQpQRkFfQ1JJXzE8LVBGQShQX0NSSV8xJG1lYW4sYXMudmVjdG9yKENPVklEX3Nlcmllc19Db3N0YVJpY2FfVGVtcF9UZXN0KSkKCkVycm9ycy5DUklfMTwtYyhSRV9DUklfMSxNQVBFX0NSSV8xLE1TRV9DUklfMSxQRkFfQ1JJXzEpCkVycm9ycy5DUklfMQpgYGAKCipNb2RlbCB3aXRob3V0IHRlbXBlcmF0dXJlKgoKQVJJTUEoMSwyLDApCmBgYHtyfQpBUklNQTJfQ29zdGFSaWNhPC1BcmltYShDT1ZJRF9zZXJpZXNfQ29zdGFSaWNhX1RlbXBfVHJhaW4sb3JkZXI9YygxLDIsMCkpCgpzdW1tYXJ5KEFSSU1BMl9Db3N0YVJpY2EpCmBgYAoKRm9yZWNhc3QgcHJlZGljdGlvbiB0byBjb21wYXJlCmBgYHtyfQpQX0NSSV8yPC1mb3JlY2FzdChBUklNQTJfQ29zdGFSaWNhLGg9bGVuZ3RoKENPVklEX3Nlcmllc19Db3N0YVJpY2FfVGVtcF9UZXN0KSkKYGBgCgpDYWxjdWxhdGUgZXJyb3JzOgpgYGB7cn0KUkVfQ1JJXzI8LVJFKFBfQ1JJXzIkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX0Nvc3RhUmljYV9UZW1wX1Rlc3QpKQpNQVBFX0NSSV8yPC1NQVBFKFBfQ1JJXzIkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX0Nvc3RhUmljYV9UZW1wX1Rlc3QpKQpNU0VfQ1JJXzI8LU1TRShQX0NSSV8yJG1lYW4sYXMudmVjdG9yKENPVklEX3Nlcmllc19Db3N0YVJpY2FfVGVtcF9UZXN0KSkKUEZBX0NSSV8yPC1QRkEoUF9DUklfMiRtZWFuLGFzLnZlY3RvcihDT1ZJRF9zZXJpZXNfQ29zdGFSaWNhX1RlbXBfVGVzdCkpCgpFcnJvcnMuQ1JJXzI8LWMoUkVfQ1JJXzIsTUFQRV9DUklfMixNU0VfQ1JJXzIsUEZBX0NSSV8yKQpFcnJvcnMuQ1JJXzIKYGBgCgpgYGB7cn0KRXJyb3JzPC1yYmluZChFcnJvcnMuQ1JJXzEsRXJyb3JzLkNSSV8yKQoKcm93bmFtZXMoRXJyb3JzKTwtYygiQVJJTUFYKDEsMiwxKSIsIkFSSU1BKDEsMiwwKSIpCgpjb2xuYW1lcyhFcnJvcnMpPC1jKCJSZWxhdGl2ZSBlcnJvciIsIk1lYW4gQWJzICUgZXJyb3IiLCJNZWFuIFNxdWFyZWQgRXJyb3IiLCJQRkEiKQoKRXJyb3JzPC1hcy5kYXRhLmZyYW1lKEVycm9ycykKCm1heGltdW08LWFwcGx5KEVycm9ycywyLG1heCkKCm1pbmltdW08LWFwcGx5KEVycm9ycywyLG1pbikKCkVycm9yczwtcmJpbmQobWluaW11bSxFcnJvcnMpCgpFcnJvcnM8LXJiaW5kKG1heGltdW0sRXJyb3JzKQpgYGAKCmBgYHtyfQpyYWRhcmNoYXJ0KEVycm9ycyxtYXhtaW49VFJVRSxheGlzdHlwZT00LGF4aXNsYWJjb2w9InNsYXRlZ3JheTQiLAogICAgICAgICAgIGNlbnRlcnplcm89RkFMU0Usc2VnPTgsY2dsY29sPSJncmF5NjciLAogICAgICAgICAgIHBjb2w9YygiZG9kZ2VyYmx1ZTIiLCJmaXJlYnJpY2syIiwiZGFya29yYW5nZTIiLCJkYXJrb3JjaGlkMiIpLAogICAgICAgICAgIHBsdHk9MSwKICAgICAgICAgICBwbHdkPTMsCiAgICAgICAgICAgdGl0bGU9IkNvc3RhIFJpY2EgZm9yZWNhc3Q6IGVycm9yIGNvbXBhcmlzb24gb24gdGVzdCBzZXQiKQoKbGVnZW5kIDwtbGVnZW5kKDEuNSwxLCBsZWdlbmQ9YygiQVJJTUFYKDEsMiwxKSIsIkFSSU1BKDEsMiwwKSIpLAogICAgICAgICAgICAgICAgIHNlZy5sZW49LTEuNCwKICAgICAgICAgICAgICAgICB0aXRsZT0iRXJyb3JzIiwKICAgICAgICAgICAgICAgICBwY2g9MjEsIAogICAgICAgICAgICAgICAgIGJ0eT0ibiIgLGx3ZD0zLCB5LmludGVyc3A9MSwgaG9yaXo9RkFMU0UsCiAgICAgICAgICAgICAgICAgY29sPWMoImRvZGdlcmJsdWUyIiwiZmlyZWJyaWNrMiIsImRhcmtvcmFuZ2UyIiwiZGFya29yY2hpZDIiKSkKYGBgCgpDb25jbHVzaW9uOiBLZWVwIG1vZGVsIHdpdGhvdXQgdGVtcGVyYXR1cmUKCk1ha2UgbW9kZWwgd2l0aCB0aGUgb3ZlcmFsbCBzZXJpZXMKYGBge3J9CkFSSU1BX0ZpbmFsX0Nvc3RhUmljYTwtQXJpbWEoQ09WSURfRGF5X3Nlcmllc19Db3N0YVJpY2FfVGVtcCxvcmRlcj1jKDAsMiwxKSkKc3VtbWFyeShBUklNQV9GaW5hbF9Db3N0YVJpY2EpCmBgYAoKRmluYWwgZm9yZWNhc3Q6CmBgYHtyfQpGdXR1cmVfQ29zdGFSaWNhX1RlbXA8LUNvc3RhUmljYV9UZW1wJFRlbXBlcmF0dXJlX0Nvc3RhUmljYVtDb3N0YVJpY2FfVGVtcCREYXRlPm1heChDT1ZJRF8yJERhdGUyKV0KClBfQ1JJX0ZpbmFsPC1mb3JlY2FzdChBUklNQV9GaW5hbF9Db3N0YVJpY2EsaCA9IGxlbmd0aChGdXR1cmVfQ29zdGFSaWNhX1RlbXApKQpMb3dfbGltX0NSSTwtZGF0YS5mcmFtZShQX0NSSV9GaW5hbCRsb3dlcilbLDJdClVwcF9saW1fQ1JJPC1kYXRhLmZyYW1lKFBfQ1JJX0ZpbmFsJHVwcGVyKVssMl0KYGBgCgpGb3IgbWFraW5nIHRoZSBwbG90OgpgYGB7cn0KIyNEYXRhIHBlcmlvZHMKcGVyXzEgPC0gYXMuRGF0ZShhcy5jaGFyYWN0ZXIoQ09WSURfMl9EYXlfQ29zdGFSaWNhX1RlbXAkRGF0ZTIpKQpwZXJfMiA8LSBzZXEoYXMuRGF0ZShtYXgoQ09WSURfMl9EYXlfQ29zdGFSaWNhX1RlbXAkRGF0ZTIpKzEsZm9ybWF0PSIlWS0lbS0lZCIpLCBhcy5EYXRlKCIyMDIwLTA0LTMwIixmb3JtYXQ9IiVZLSVtLSVkIiksIjEgZGF5IikKCgojIE1lcmdlIGZvcmVjYXN0ICsgYWN0dWFscwpkYXRhIDwtIHh0cyhDT1ZJRF9EYXlfc2VyaWVzX0Nvc3RhUmljYV9UZW1wLG9yZGVyLmJ5PXBlcl8xKSAKZGF0YU5BIDwtIHJlcChOQSwgbGVuZ3RoKGRhdGEpKQpBIDwtIGNiaW5kKGRhdGEsZGF0YU5BLGRhdGFOQSxkYXRhTkEpCgoKTG93X2xpbV9DUkkgPC0geHRzKExvd19saW1fQ1JJLG9yZGVyLmJ5PXBlcl8yKQpGb3JlY2FzdF9DUkkgPC0geHRzKFBfQ1JJX0ZpbmFsJG1lYW4sb3JkZXIuYnk9cGVyXzIpClVwcF9saW1fQ1JJIDwtIHh0cyhVcHBfbGltX0NSSSxvcmRlci5ieT1wZXJfMikKcHJlZE5BIDwtIHJlcChOQSwgbGVuZ3RoKEZvcmVjYXN0X0NSSSkpCkIgPC0gY2JpbmQocHJlZE5BLCBMb3dfbGltX0NSSSwgRm9yZWNhc3RfQ1JJLCBVcHBfbGltX0NSSSkKCmFsbF9zZXJpZXNfQ1JJIDwtIGRhdGEuZnJhbWUocmJpbmQoYXMubWF0cml4KEEpLGFzLm1hdHJpeChCKSkpCmNvbG5hbWVzKGFsbF9zZXJpZXNfQ1JJKSA8LSBjKCdBY3R1YWwnLCAnTG93ZXJfbGltaXQnLCAnRm9yZWNhc3QnLCAnVXBwZXJfbGltaXQnKQpgYGAKCmBgYHtyfQpkeWdyYXBoKGFsbF9zZXJpZXNfQ1JJLCBtYWluPSJTQVJTLUNPVjItb3V0YnJlYWs6IFRvdGFsIENvc3RhIFJpY2EgY2FzZXMiLHhsYWI9IkRhdGUiLCB5bGFiPSJOb3ZlbCBjb3JvbmF2aXJ1cyBjYXNlcyIsd2lkdGggPSA3NTApJT4lCiAgZHlTZXJpZXMoYygnTG93ZXJfbGltaXQnLCAnRm9yZWNhc3QnLCAnVXBwZXJfbGltaXQnKSxsYWJlbD0iRm9yZWNhc3QiLHN0cm9rZVdpZHRoPTIsCiAgICAgICAgICAgZHJhd1BvaW50cyA9IFRSVUUsIHBvaW50U2l6ZSA9IDIsIGNvbG9yPXJnYigxODkvMjU1LDQ0LzI1NSw0Ny8yNTUpKSAlPiUKICBkeVNlcmllcygiQWN0dWFsIixkcmF3UG9pbnRzID0gVFJVRSwgc3Ryb2tlV2lkdGg9MiwgcG9pbnRTaXplID0gMiwKICAgICAgICAgICBjb2xvcj1yZ2IoMTAvMjU1LDQ0LzI1NSwxMTkvMjU1KSkgJT4lIAogIGR5UmFuZ2VTZWxlY3RvcigpCgpgYGAKCiMjIE1leGljbwoKIVtdKE1leGljb0ZsYWcucG5nKXt3aWR0aD00MCV9CgoqTW9kZWwgd2l0aCB0ZW1wZXJhdHVyZSoKCmBgYHtyfQpNZXhpY29fVGVtcDwtcmVhZC5jc3YoIk1leGljb19UZW1wZXJhdHVyZS5jc3YiLHNlcD0iOyIpCk1leGljb19UZW1wJERhdGU8LWFzLkRhdGUoTWV4aWNvX1RlbXAkRGF0ZSxmb3JtYXQ9IiVkLyVtLyVZIikKYGBgCgpgYGB7cn0KQ09WSURfMl9EYXlfTWV4aWNvPC0gQ09WSURfMiAlPiUgCiAgZmlsdGVyKENvdW50cnkuUmVnaW9uICVpbiUgYygiTWV4aWNvIikpICU+JSAKICBncm91cF9ieShEYXRlMikgJT4lIHN1bW1hcmlzZShXb3JsZF9jb25maXJtZWQ9c3VtKENvbmZpcm1lZCkpCgpDT1ZJRF8yX0RheV9NZXhpY29fVGVtcDwtQ09WSURfMl9EYXlfTWV4aWNvICU+JQogIGlubmVyX2pvaW4oTWV4aWNvX1RlbXAsYnk9YygiRGF0ZTIiPSJEYXRlIikpCgpDT1ZJRF9EYXlfc2VyaWVzX01leGljb19UZW1wPC14dHMoQ09WSURfMl9EYXlfTWV4aWNvX1RlbXAkV29ybGRfY29uZmlybWVkLCBvcmRlci5ieT1DT1ZJRF8yX0RheV9NZXhpY29fVGVtcCREYXRlMikKYGBgCgpgYGB7cn0KQ09WSURfc2VyaWVzX01leGljb19UcmFpbjwtQ09WSURfRGF5X3Nlcmllc19NZXhpY29fVGVtcFsxOjc0XSAjVW50aWwgQXByaWwgNHRoCkNPVklEX3Nlcmllc19NZXhpY29fVGVzdDwtQ09WSURfRGF5X3Nlcmllc19NZXhpY29fVGVtcFs3NTpsZW5ndGgoQ09WSURfRGF5X3Nlcmllc19NZXhpY29fVGVtcCldICNGcm9tIEFwcmlsIDV0aCBvbndhcmRzCmBgYAoKR2V0IHRoZSBkaWZmZXJlbnRpYXRlZCBzZXJpZXM6IEluIHRoaXMgY2FzZSB1bnRpbCBhIHNlY29uZCBkaWZmZXJlbmNlIHdhcyBzdWl0YWJsZQpgYGB7cn0KcGxvdChkaWZmKGRpZmYoQ09WSURfc2VyaWVzX01leGljb19UcmFpbikpLHR5cGU9ImwiLG1haW49Ik1leGljbzogMm5kIGRpZmZlcmVuY2UiKSAKYGBgCgpDb3JyZWxvZ3JhbXMgb2Ygc2Vjb25kIGRpZmVyZW5jZToKYGBge3J9CnRzZGlzcGxheShkaWZmKGRpZmYoQ09WSURfc2VyaWVzX01leGljb19UcmFpbikpLG1haW49Ik1leGljbyBBQ0YgYW5kIFBBQ0YgY29ycmVsb2dyYW1zIikKYGBgCkJ5IGFuYWx5emluZyB0aGUgYXV0b2NvcnJlbGF0aW9uIGFuZCBwYXJ0aWFsIGF1dG9jb3JyZWxhdGlvbiBmdW5jdGlvbiBwbG90cy4gQSBtb2RlbCB3aXRoIDIgYXV0b3JlZ3Jlc3NpdmUgdGVybXMsIGFuZCAzIG1vdmluZyBhdmVyYWdlIHRlcm1zIHdpbGwgYmUgZml0dGVkCgoKCkFyaW1hIG1vZGVsOiBBUklNQSgyLDIsMykKYGBge3J9CkFSSU1BMV9NZXhpY288LUFyaW1hKENPVklEX3Nlcmllc19NZXhpY29fVHJhaW4sb3JkZXI9YygyLDIsMykseHJlZz1DT1ZJRF8yX0RheV9NZXhpY29fVGVtcCRUZW1wZXJhdHVyZV9NZXhpY29bMTo3NF0pCnN1bW1hcnkoQVJJTUExX01leGljbykKYGBgCgpGb3JlY2FzdCBwcmVkaWN0aW9uIHRvIGNvbXBhcmUKYGBge3J9ClRlc3RfTWV4aWNvX1RlbXA8LUNPVklEXzJfRGF5X01leGljb19UZW1wJFRlbXBlcmF0dXJlX01leGljb1s3NTpsZW5ndGgoQ09WSURfRGF5X3Nlcmllc19NZXhpY29fVGVtcCldCgoKUF9NRVhfMTwtZm9yZWNhc3QoQVJJTUExX01leGljbyx4cmVnID0gVGVzdF9NZXhpY29fVGVtcCkKYGBgCgpDYWxjdWxhdGUgZXJyb3JzOgpgYGB7cn0KUkVfTUVYXzE8LVJFKFBfTUVYXzEkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX01leGljb19UZXN0KSkKTUFQRV9NRVhfMTwtTUFQRShQX01FWF8xJG1lYW4sYXMudmVjdG9yKENPVklEX3Nlcmllc19NZXhpY29fVGVzdCkpCk1TRV9NRVhfMTwtTVNFKFBfTUVYXzEkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX01leGljb19UZXN0KSkKUEZBX01FWF8xPC1QRkEoUF9NRVhfMSRtZWFuLGFzLnZlY3RvcihDT1ZJRF9zZXJpZXNfTWV4aWNvX1Rlc3QpKQoKRXJyb3JzLk1FWF8xPC1jKFJFX01FWF8xLE1BUEVfTUVYXzEsTVNFX01FWF8xLFBGQV9NRVhfMSkKRXJyb3JzLk1FWF8xCmBgYAoKKk1vZGVsIHdpdGhvdXQgdGVtcGVyYXR1cmUqCgpBUklNQSgyLDIsNCkKYGBge3J9CkFSSU1BMl9NZXhpY288LUFyaW1hKENPVklEX3Nlcmllc19NZXhpY29fVHJhaW4sb3JkZXI9YygyLDIsNCkpCgpzdW1tYXJ5KEFSSU1BMl9NZXhpY28pCmBgYAoKRm9yZWNhc3QgcHJlZGljdGlvbiB0byBjb21wYXJlCmBgYHtyfQpQX01FWF8yPC1mb3JlY2FzdChBUklNQTJfTWV4aWNvLGg9bGVuZ3RoKENPVklEX3Nlcmllc19NZXhpY29fVGVzdCkpCmBgYAoKQ2FsY3VsYXRlIGVycm9yczoKYGBge3J9ClJFX01FWF8yPC1SRShQX01FWF8yJG1lYW4sYXMudmVjdG9yKENPVklEX3Nlcmllc19NZXhpY29fVGVzdCkpCk1BUEVfTUVYXzI8LU1BUEUoUF9NRVhfMiRtZWFuLGFzLnZlY3RvcihDT1ZJRF9zZXJpZXNfTWV4aWNvX1Rlc3QpKQpNU0VfTUVYXzI8LU1TRShQX01FWF8yJG1lYW4sYXMudmVjdG9yKENPVklEX3Nlcmllc19NZXhpY29fVGVzdCkpClBGQV9NRVhfMjwtUEZBKFBfTUVYXzIkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX01leGljb19UZXN0KSkKCkVycm9ycy5NRVhfMjwtYyhSRV9NRVhfMixNQVBFX01FWF8yLE1TRV9NRVhfMixQRkFfTUVYXzIpCkVycm9ycy5NRVhfMgpgYGAKCmBgYHtyfQpFcnJvcnM8LXJiaW5kKEVycm9ycy5NRVhfMSxFcnJvcnMuTUVYXzIpCgpyb3duYW1lcyhFcnJvcnMpPC1jKCJBUklNQVgoMiwyLDMpIiwiQVJJTUEoMiwyLDQpIikKCmNvbG5hbWVzKEVycm9ycyk8LWMoIlJlbGF0aXZlIGVycm9yIiwiTWVhbiBBYnMgJSBlcnJvciIsIk1lYW4gU3F1YXJlZCBFcnJvciIsIlBGQSIpCgpFcnJvcnM8LWFzLmRhdGEuZnJhbWUoRXJyb3JzKQoKbWF4aW11bTwtYXBwbHkoRXJyb3JzLDIsbWF4KQoKbWluaW11bTwtYXBwbHkoRXJyb3JzLDIsbWluKQoKRXJyb3JzPC1yYmluZChtaW5pbXVtLEVycm9ycykKCkVycm9yczwtcmJpbmQobWF4aW11bSxFcnJvcnMpCmBgYAoKYGBge3J9CnJhZGFyY2hhcnQoRXJyb3JzLG1heG1pbj1UUlVFLGF4aXN0eXBlPTQsYXhpc2xhYmNvbD0ic2xhdGVncmF5NCIsCiAgICAgICAgICAgY2VudGVyemVybz1GQUxTRSxzZWc9OCxjZ2xjb2w9ImdyYXk2NyIsCiAgICAgICAgICAgcGNvbD1jKCJkb2RnZXJibHVlMiIsImZpcmVicmljazIiLCJkYXJrb3JhbmdlMiIsImRhcmtvcmNoaWQyIiksCiAgICAgICAgICAgcGx0eT0xLAogICAgICAgICAgIHBsd2Q9MywKICAgICAgICAgICB0aXRsZT0iTWV4aWNvIGZvcmVjYXN0OiBlcnJvciBjb21wYXJpc29uIG9uIHRlc3Qgc2V0IikKCmxlZ2VuZCA8LWxlZ2VuZCgxLjUsMSwgbGVnZW5kPWMoIkFSSU1BWCgyLDIsMykiLCJBUklNQSgyLDIsNCkiKSwKICAgICAgICAgICAgICAgICBzZWcubGVuPS0xLjQsCiAgICAgICAgICAgICAgICAgdGl0bGU9IkVycm9ycyIsCiAgICAgICAgICAgICAgICAgcGNoPTIxLCAKICAgICAgICAgICAgICAgICBidHk9Im4iICxsd2Q9MywgeS5pbnRlcnNwPTEsIGhvcml6PUZBTFNFLAogICAgICAgICAgICAgICAgIGNvbD1jKCJkb2RnZXJibHVlMiIsImZpcmVicmljazIiLCJkYXJrb3JhbmdlMiIsImRhcmtvcmNoaWQyIikpCmBgYAoKQ29uY2x1c2lvbjogS2VlcCBtb2RlbCB3aXRoIHRlbXBlcmF0dXJlCgpNYWtlIG1vZGVsIHdpdGggdGhlIG92ZXJhbGwgc2VyaWVzCmBgYHtyfQpBUklNQV9GaW5hbF9NZXhpY288LUFyaW1hKENPVklEX0RheV9zZXJpZXNfTWV4aWNvX1RlbXAsb3JkZXI9YygyLDIsMSkseHJlZz1DT1ZJRF8yX0RheV9NZXhpY29fVGVtcCRUZW1wZXJhdHVyZV9NZXhpY28pCgpzdW1tYXJ5KEFSSU1BX0ZpbmFsX01leGljbykKYGBgCgpGaW5hbCBmb3JlY2FzdDoKYGBge3J9CkZ1dHVyZV9NZXhpY29fVGVtcDwtTWV4aWNvX1RlbXAkVGVtcGVyYXR1cmVfTWV4aWNvW01leGljb19UZW1wJERhdGU+bWF4KENPVklEXzIkRGF0ZTIpXQoKUF9NRVhfRmluYWw8LWZvcmVjYXN0KEFSSU1BX0ZpbmFsX01leGljbyx4cmVnID0gRnV0dXJlX01leGljb19UZW1wKQpMb3dfbGltX01FWDwtZGF0YS5mcmFtZShQX01FWF9GaW5hbCRsb3dlcilbLDJdClVwcF9saW1fTUVYPC1kYXRhLmZyYW1lKFBfTUVYX0ZpbmFsJHVwcGVyKVssMl0KYGBgCgpGb3IgbWFraW5nIHRoZSBwbG90OgpgYGB7cn0KIyNEYXRhIHBlcmlvZHMKcGVyXzEgPC0gYXMuRGF0ZShhcy5jaGFyYWN0ZXIoQ09WSURfMl9EYXlfTWV4aWNvX1RlbXAkRGF0ZTIpKQpwZXJfMiA8LSBzZXEoYXMuRGF0ZShtYXgoQ09WSURfMl9EYXlfTWV4aWNvX1RlbXAkRGF0ZTIpKzEsZm9ybWF0PSIlWS0lbS0lZCIpLCBhcy5EYXRlKCIyMDIwLTA0LTMwIixmb3JtYXQ9IiVZLSVtLSVkIiksIjEgZGF5IikKCgojIE1lcmdlIGZvcmVjYXN0ICsgYWN0dWFscwpkYXRhIDwtIHh0cyhDT1ZJRF9EYXlfc2VyaWVzX01leGljb19UZW1wLG9yZGVyLmJ5PXBlcl8xKSAKZGF0YU5BIDwtIHJlcChOQSwgbGVuZ3RoKGRhdGEpKQpBIDwtIGNiaW5kKGRhdGEsZGF0YU5BLGRhdGFOQSxkYXRhTkEpCgoKTG93X2xpbV9NRVggPC0geHRzKExvd19saW1fTUVYLG9yZGVyLmJ5PXBlcl8yKQpGb3JlY2FzdF9NRVggPC0geHRzKFBfTUVYX0ZpbmFsJG1lYW4sb3JkZXIuYnk9cGVyXzIpClVwcF9saW1fTUVYIDwtIHh0cyhVcHBfbGltX01FWCxvcmRlci5ieT1wZXJfMikKcHJlZE5BIDwtIHJlcChOQSwgbGVuZ3RoKEZvcmVjYXN0X01FWCkpCkIgPC0gY2JpbmQocHJlZE5BLCBMb3dfbGltX01FWCwgRm9yZWNhc3RfTUVYLCBVcHBfbGltX01FWCkKCmFsbF9zZXJpZXNfTUVYIDwtIGRhdGEuZnJhbWUocmJpbmQoYXMubWF0cml4KEEpLGFzLm1hdHJpeChCKSkpCmNvbG5hbWVzKGFsbF9zZXJpZXNfTUVYKSA8LSBjKCdBY3R1YWwnLCAnTG93ZXJfbGltaXQnLCAnRm9yZWNhc3QnLCAnVXBwZXJfbGltaXQnKQpgYGAKCmBgYHtyfQpkeWdyYXBoKGFsbF9zZXJpZXNfTUVYLCBtYWluPSJTQVJTLUNPVjItb3V0YnJlYWs6IFRvdGFsIE1leGljbyBjYXNlcyIseGxhYj0iRGF0ZSIsIHlsYWI9Ik5vdmVsIGNvcm9uYXZpcnVzIGNhc2VzIix3aWR0aCA9IDc1MCklPiUKICBkeVNlcmllcyhjKCdMb3dlcl9saW1pdCcsICdGb3JlY2FzdCcsICdVcHBlcl9saW1pdCcpLGxhYmVsPSJGb3JlY2FzdCIsc3Ryb2tlV2lkdGg9MiwKICAgICAgICAgICBkcmF3UG9pbnRzID0gVFJVRSwgcG9pbnRTaXplID0gMiwgY29sb3I9cmdiKDE4OS8yNTUsNDQvMjU1LDQ3LzI1NSkpICU+JQogIGR5U2VyaWVzKCJBY3R1YWwiLGRyYXdQb2ludHMgPSBUUlVFLCBzdHJva2VXaWR0aD0yLCBwb2ludFNpemUgPSAyLAogICAgICAgICAgIGNvbG9yPXJnYig0My8yNTUsMTAyLzI1NSw3My8yNTUpKSAlPiUgCiAgZHlSYW5nZVNlbGVjdG9yKCkKCmBgYAoKIyMgSXRhbHkKCiFbXShJdGFseUZsYWcucG5nKXt3aWR0aD00MCV9CgoqTW9kZWwgd2l0aCB0ZW1wZXJhdHVyZSoKCmBgYHtyfQpJdGFseV9UZW1wPC1yZWFkLmNzdigiSXRhbHlfVGVtcGVyYXR1cmUuY3N2IixzZXA9IjsiKQpJdGFseV9UZW1wJERhdGU8LWFzLkRhdGUoSXRhbHlfVGVtcCREYXRlLGZvcm1hdD0iJWQvJW0vJVkiKQpgYGAKCmBgYHtyfQpDT1ZJRF8yX0RheV9JdGFseTwtIENPVklEXzIgJT4lIAogIGZpbHRlcihDb3VudHJ5LlJlZ2lvbiAlaW4lIGMoIkl0YWx5IikpICU+JSAKICBncm91cF9ieShEYXRlMikgJT4lIHN1bW1hcmlzZShXb3JsZF9jb25maXJtZWQ9c3VtKENvbmZpcm1lZCkpCgpDT1ZJRF8yX0RheV9JdGFseV9UZW1wPC1DT1ZJRF8yX0RheV9JdGFseSAlPiUKICBpbm5lcl9qb2luKEl0YWx5X1RlbXAsYnk9YygiRGF0ZTIiPSJEYXRlIikpCgpDT1ZJRF9EYXlfc2VyaWVzX0l0YWx5X1RlbXA8LXh0cyhDT1ZJRF8yX0RheV9JdGFseV9UZW1wJFdvcmxkX2NvbmZpcm1lZCwgb3JkZXIuYnk9Q09WSURfMl9EYXlfSXRhbHlfVGVtcCREYXRlMikKYGBgCgpgYGB7cn0KQ09WSURfRGF5X3Nlcmllc19JdGFseV9UcmFpbjwtQ09WSURfRGF5X3Nlcmllc19JdGFseV9UZW1wWzE6NjRdICNVbnRpbCBNYXJjaCAyNXRoCkNPVklEX0RheV9zZXJpZXNfSXRhbHlfVGVzdDwtQ09WSURfRGF5X3Nlcmllc19JdGFseV9UZW1wWzY1Omxlbmd0aChDT1ZJRF9EYXlfc2VyaWVzX0l0YWx5X1RlbXApXSAjRnJvbSBNYXJjaCAyNnRoIG9ud2FyZHMKYGBgCgpHZXQgdGhlIGxhZ2dlZCBkaWZmZXJlbmNlOgpgYGB7cn0KcGxvdChkaWZmKGRpZmYoQ09WSURfRGF5X3Nlcmllc19JdGFseV9UcmFpbikpLHR5cGU9ImwiLG1haW49Ikl0YWx5OiAybmQgZGlmZmVyZW5jZSIpIApgYGAKCkNvcnJlbG9ncmFtcyBvZiBmaXJzdCBkaWZlcmVuY2U6CmBgYHtyfQp0c2Rpc3BsYXkoZGlmZihkaWZmKENPVklEX0RheV9zZXJpZXNfSXRhbHlfVHJhaW4pKSxtYWluPSJJdGFseSBBQ0YgYW5kIFBBQ0YgY29ycmVsb2dyYW1zIikKYGBgCkJ5IGFuYWx5emluZyB0aGUgYXV0b2NvcnJlbGF0aW9uIGFuZCBwYXJ0aWFsIGF1dG9jb3JyZWxhdGlvbiBmdW5jdGlvbiBwbG90cy4gQSBtb2RlbCB3aXRoIDEgYXV0b3JlZ3Jlc3NpdmUgdGVybSB3aWxsIGJlIGZpdHRlZC4KCgpBcmltYSBtb2RlbDogQVJJTUEoMSwyLDApCmBgYHtyfQpBUklNQTFfSXRhbHk8LUFyaW1hKENPVklEX0RheV9zZXJpZXNfSXRhbHlfVHJhaW4sb3JkZXI9YygxLDIsMCkseHJlZz1DT1ZJRF8yX0RheV9JdGFseV9UZW1wJFRlbXBlcmF0dXJlX0l0YWx5WzE6NjRdKQoKc3VtbWFyeShBUklNQTFfSXRhbHkpCmBgYAoKRm9yZWNhc3QgcHJlZGljdGlvbiB0byBjb21wYXJlCmBgYHtyfQpUZXN0X0l0YWx5X1RlbXA8LUNPVklEXzJfRGF5X0l0YWx5X1RlbXAkVGVtcGVyYXR1cmVfSXRhbHlbNjU6bGVuZ3RoKENPVklEX0RheV9zZXJpZXNfSXRhbHlfVGVtcCldCgoKUF9JVEFfMTwtZm9yZWNhc3QoQVJJTUExX0l0YWx5LHhyZWcgPSBUZXN0X0l0YWx5X1RlbXApCmBgYAoKQ2FsY3VsYXRlIGVycm9yczoKYGBge3J9ClJFX0lUQV8xPC1SRShQX0lUQV8xJG1lYW4sYXMudmVjdG9yKENPVklEX0RheV9zZXJpZXNfSXRhbHlfVGVzdCkpCk1BUEVfSVRBXzE8LU1BUEUoUF9JVEFfMSRtZWFuLGFzLnZlY3RvcihDT1ZJRF9EYXlfc2VyaWVzX0l0YWx5X1Rlc3QpKQpNU0VfSVRBXzE8LU1TRShQX0lUQV8xJG1lYW4sYXMudmVjdG9yKENPVklEX0RheV9zZXJpZXNfSXRhbHlfVGVzdCkpClBGQV9JVEFfMTwtUEZBKFBfSVRBXzEkbWVhbixhcy52ZWN0b3IoQ09WSURfRGF5X3Nlcmllc19JdGFseV9UZXN0KSkKCkVycm9ycy5JVEFfMTwtYyhSRV9JVEFfMSxNQVBFX0lUQV8xLE1TRV9JVEFfMSxQRkFfSVRBXzEpCkVycm9ycy5JVEFfMQpgYGAKCipNb2RlbCB3aXRob3V0IHRlbXBlcmF0dXJlKgoKQVJJTUEoMSwyLDApCmBgYHtyfQpBUklNQTJfSXRhbHk8LUFyaW1hKENPVklEX0RheV9zZXJpZXNfSXRhbHlfVHJhaW4sb3JkZXI9YygxLDIsMCkpCnN1bW1hcnkoQVJJTUEyX0l0YWx5KQpgYGAKCkZvcmVjYXN0IHByZWRpY3Rpb24gdG8gY29tcGFyZQpgYGB7cn0KUF9JVEFfMjwtZm9yZWNhc3QoQVJJTUEyX0l0YWx5LGg9bGVuZ3RoKENPVklEX0RheV9zZXJpZXNfSXRhbHlfVGVzdCkpCmBgYAoKQ2FsY3VsYXRlIGVycm9yczoKYGBge3J9ClJFX0lUQV8yPC1SRShQX0lUQV8yJG1lYW4sYXMudmVjdG9yKENPVklEX0RheV9zZXJpZXNfSXRhbHlfVGVzdCkpCk1BUEVfSVRBXzI8LU1BUEUoUF9JVEFfMiRtZWFuLGFzLnZlY3RvcihDT1ZJRF9EYXlfc2VyaWVzX0l0YWx5X1Rlc3QpKQpNU0VfSVRBXzI8LU1TRShQX0lUQV8yJG1lYW4sYXMudmVjdG9yKENPVklEX0RheV9zZXJpZXNfSXRhbHlfVGVzdCkpClBGQV9JVEFfMjwtUEZBKFBfSVRBXzIkbWVhbixhcy52ZWN0b3IoQ09WSURfRGF5X3Nlcmllc19JdGFseV9UZXN0KSkKCkVycm9ycy5JVEFfMjwtYyhSRV9JVEFfMixNQVBFX0lUQV8yLE1TRV9JVEFfMixQRkFfSVRBXzIpCkVycm9ycy5JVEFfMgpgYGAKCmBgYHtyfQpFcnJvcnM8LXJiaW5kKEVycm9ycy5JVEFfMSxFcnJvcnMuSVRBXzIpCgpyb3duYW1lcyhFcnJvcnMpPC1jKCJBUklNQVgoMSwyLDApIiwiQVJJTUEoMSwyLDApIikKCmNvbG5hbWVzKEVycm9ycyk8LWMoIlJlbGF0aXZlIGVycm9yIiwiTWVhbiBBYnMgJSBlcnJvciIsIk1lYW4gU3F1YXJlZCBFcnJvciIsIlBGQSIpCgpFcnJvcnM8LWFzLmRhdGEuZnJhbWUoRXJyb3JzKQoKbWF4aW11bTwtYXBwbHkoRXJyb3JzLDIsbWF4KQoKbWluaW11bTwtYXBwbHkoRXJyb3JzLDIsbWluKQoKRXJyb3JzPC1yYmluZChtaW5pbXVtLEVycm9ycykKCkVycm9yczwtcmJpbmQobWF4aW11bSxFcnJvcnMpCmBgYAoKYGBge3J9CnJhZGFyY2hhcnQoRXJyb3JzLG1heG1pbj1UUlVFLGF4aXN0eXBlPTQsYXhpc2xhYmNvbD0ic2xhdGVncmF5NCIsCiAgICAgICAgICAgY2VudGVyemVybz1GQUxTRSxzZWc9OCxjZ2xjb2w9ImdyYXk2NyIsCiAgICAgICAgICAgcGNvbD1jKCJkb2RnZXJibHVlMiIsImZpcmVicmljazIiLCJkYXJrb3JhbmdlMiIsImRhcmtvcmNoaWQyIiksCiAgICAgICAgICAgcGx0eT0xLAogICAgICAgICAgIHBsd2Q9MywKICAgICAgICAgICB0aXRsZT0iSXRhbHkgZm9yZWNhc3Q6IGVycm9yIGNvbXBhcmlzb24gb24gdGVzdCBzZXQiKQoKbGVnZW5kIDwtbGVnZW5kKDEuNSwxLCBsZWdlbmQ9YygiQVJJTUFYKDEsMiwwKSIsIkFSSU1BKDEsMiwwKSIpLAogICAgICAgICAgICAgICAgIHNlZy5sZW49LTEuNCwKICAgICAgICAgICAgICAgICB0aXRsZT0iRXJyb3JzIiwKICAgICAgICAgICAgICAgICBwY2g9MjEsIAogICAgICAgICAgICAgICAgIGJ0eT0ibiIgLGx3ZD0zLCB5LmludGVyc3A9MSwgaG9yaXo9RkFMU0UsCiAgICAgICAgICAgICAgICAgY29sPWMoImRvZGdlcmJsdWUyIiwiZmlyZWJyaWNrMiIsImRhcmtvcmFuZ2UyIiwiZGFya29yY2hpZDIiKSkKYGBgCgpDb25jbHVzaW9uOiBLZWVwIG1vZGVsIHdpdGhvdXQgdGVtcGVyYXR1cmUKCk1ha2UgbW9kZWwgd2l0aCB0aGUgb3ZlcmFsbCBzZXJpZXMKYGBge3J9CkFSSU1BX0ZpbmFsX0l0YWx5PC1BcmltYShDT1ZJRF9EYXlfc2VyaWVzX0l0YWx5X1RlbXAsb3JkZXI9YygxLDIsMCkpCnN1bW1hcnkoQVJJTUFfRmluYWxfSXRhbHkpCmBgYAoKRmluYWwgZm9yZWNhc3Q6CmBgYHtyfQpGdXR1cmVfSXRhbHlfVGVtcDwtSXRhbHlfVGVtcCRUZW1wZXJhdHVyZV9JdGFseVtJdGFseV9UZW1wJERhdGU+bWF4KENPVklEXzIkRGF0ZTIpXQoKUF9JVEFfRmluYWw8LWZvcmVjYXN0KEFSSU1BX0ZpbmFsX0l0YWx5LGg9bGVuZ3RoKEZ1dHVyZV9JdGFseV9UZW1wKSkKTG93X2xpbV9JVEE8LWRhdGEuZnJhbWUoUF9JVEFfRmluYWwkbG93ZXIpWywyXQpVcHBfbGltX0lUQTwtZGF0YS5mcmFtZShQX0lUQV9GaW5hbCR1cHBlcilbLDJdCmBgYAoKRm9yIG1ha2luZyB0aGUgcGxvdDoKYGBge3J9CiMjRGF0YSBwZXJpb2RzCnBlcl8xIDwtIGFzLkRhdGUoYXMuY2hhcmFjdGVyKENPVklEXzJfRGF5X0l0YWx5X1RlbXAkRGF0ZTIpKQpwZXJfMiA8LSBzZXEoYXMuRGF0ZShtYXgoQ09WSURfMl9EYXlfSXRhbHlfVGVtcCREYXRlMikrMSxmb3JtYXQ9IiVZLSVtLSVkIiksIGFzLkRhdGUoIjIwMjAtMDQtMzAiLGZvcm1hdD0iJVktJW0tJWQiKSwiMSBkYXkiKQoKCiMgTWVyZ2UgZm9yZWNhc3QgKyBhY3R1YWxzCmRhdGEgPC0geHRzKENPVklEX0RheV9zZXJpZXNfSXRhbHlfVGVtcCxvcmRlci5ieT1wZXJfMSkgCmRhdGFOQSA8LSByZXAoTkEsIGxlbmd0aChkYXRhKSkKQSA8LSBjYmluZChkYXRhLGRhdGFOQSxkYXRhTkEsZGF0YU5BKQoKCkxvd19saW1fSVRBIDwtIHh0cyhMb3dfbGltX0lUQSxvcmRlci5ieT1wZXJfMikKRm9yZWNhc3RfSVRBIDwtIHh0cyhQX0lUQV9GaW5hbCRtZWFuLG9yZGVyLmJ5PXBlcl8yKQpVcHBfbGltX0lUQSA8LSB4dHMoVXBwX2xpbV9JVEEsb3JkZXIuYnk9cGVyXzIpCnByZWROQSA8LSByZXAoTkEsIGxlbmd0aChGb3JlY2FzdF9JVEEpKQpCIDwtIGNiaW5kKHByZWROQSwgTG93X2xpbV9JVEEsIEZvcmVjYXN0X0lUQSwgVXBwX2xpbV9JVEEpCgphbGxfc2VyaWVzX0lUQSA8LSBkYXRhLmZyYW1lKHJiaW5kKGFzLm1hdHJpeChBKSxhcy5tYXRyaXgoQikpKQpjb2xuYW1lcyhhbGxfc2VyaWVzX0lUQSkgPC0gYygnQWN0dWFsJywgJ0xvd2VyX2xpbWl0JywgJ0ZvcmVjYXN0JywgJ1VwcGVyX2xpbWl0JykKYGBgCgpgYGB7cn0KZHlncmFwaChhbGxfc2VyaWVzX0lUQSwgbWFpbj0iU0FSUy1DT1YyLW91dGJyZWFrOiBUb3RhbCBJdGFseSBjYXNlcyIseGxhYj0iRGF0ZSIsIHlsYWI9Ik5vdmVsIGNvcm9uYXZpcnVzIGNhc2VzIix3aWR0aCA9IDc1MCklPiUKICBkeVNlcmllcyhjKCdMb3dlcl9saW1pdCcsICdGb3JlY2FzdCcsICdVcHBlcl9saW1pdCcpLGxhYmVsPSJGb3JlY2FzdCIsc3Ryb2tlV2lkdGg9MiwKICAgICAgICAgICBkcmF3UG9pbnRzID0gVFJVRSwgcG9pbnRTaXplID0gMiwgY29sb3I9cmdiKDE5MC8yNTUsNTkvMjU1LDYxLzI1NSkpICU+JQogIGR5U2VyaWVzKCJBY3R1YWwiLGRyYXdQb2ludHMgPSBUUlVFLCBzdHJva2VXaWR0aD0yLCBwb2ludFNpemUgPSAyLAogICAgICAgICAgIGNvbG9yPXJnYig2NC8yNTUsMTQzLzI1NSw3OC8yNTUpKSAlPiUgCiAgZHlSYW5nZVNlbGVjdG9yKCkKCmBgYAoKCiMjIExlYmFub24KCiFbXShMZWJhbm9uRmxhZy5wbmcpe3dpZHRoPTQwJX0KCipNb2RlbCB3aXRoIHRlbXBlcmF0dXJlKgoKYGBge3J9CkxlYmFub25fVGVtcDwtcmVhZC5jc3YoIkxlYmFub25fVGVtcGVyYXR1cmUuY3N2IixzZXA9IjsiKQpMZWJhbm9uX1RlbXAkRGF0ZTwtYXMuRGF0ZShMZWJhbm9uX1RlbXAkRGF0ZSxmb3JtYXQ9IiVkLyVtLyVZIikKYGBgCgpgYGB7cn0KQ09WSURfMl9EYXlfTGViYW5vbjwtIENPVklEXzIgJT4lIAogIGZpbHRlcihDb3VudHJ5LlJlZ2lvbiAlaW4lIGMoIkxlYmFub24iKSkgJT4lIAogIGdyb3VwX2J5KERhdGUyKSAlPiUgc3VtbWFyaXNlKFdvcmxkX2NvbmZpcm1lZD1zdW0oQ29uZmlybWVkKSkKCkNPVklEXzJfRGF5X0xlYmFub25fVGVtcDwtQ09WSURfMl9EYXlfTGViYW5vbiAlPiUKICBpbm5lcl9qb2luKExlYmFub25fVGVtcCxieT1jKCJEYXRlMiI9IkRhdGUiKSkKCkNPVklEX0RheV9zZXJpZXNfTGViYW5vbl9UZW1wPC14dHMoQ09WSURfMl9EYXlfTGViYW5vbl9UZW1wJFdvcmxkX2NvbmZpcm1lZCwgb3JkZXIuYnk9Q09WSURfMl9EYXlfTGViYW5vbl9UZW1wJERhdGUyKQpgYGAKCmBgYHtyfQpDT1ZJRF9zZXJpZXNfTGViYW5vbl9UcmFpbjwtQ09WSURfRGF5X3Nlcmllc19MZWJhbm9uX1RlbXBbMTo3NF0gI1VudGlsIEFwcmlsIDR0aApDT1ZJRF9zZXJpZXNfTGViYW5vbl9UZXN0PC1DT1ZJRF9EYXlfc2VyaWVzX0xlYmFub25fVGVtcFs3NTpsZW5ndGgoQ09WSURfRGF5X3Nlcmllc19MZWJhbm9uX1RlbXApXSAjRnJvbSBBcHJpbCA0dGggb253YXJkcwpgYGAKCkdldCB0aGUgZGlmZmVyZW50aWF0ZWQgc2VyaWVzOiBJbiB0aGlzIGNhc2UgdW50aWwgYSBmaXJzdCBkaWZmZXJlbmNlIHdhcyBzdWl0YWJsZQpgYGB7cn0KcGxvdChkaWZmKENPVklEX3Nlcmllc19MZWJhbm9uX1RyYWluKSx0eXBlPSJsIixtYWluPSJMZWJhbm9uOiAxc3QgZGlmZmVyZW5jZSIpIApgYGAKCkNvcnJlbG9ncmFtcyBvZiBmaXJzdCBkaWZlcmVuY2U6CmBgYHtyfQp0c2Rpc3BsYXkoZGlmZihDT1ZJRF9zZXJpZXNfTGViYW5vbl9UcmFpbiksbWFpbj0iTGViYW5vbiBBQ0YgYW5kIFBBQ0YgY29ycmVsb2dyYW1zIikKYGBgCgpBcmltYSBtb2RlbDogQVJJTUEoMSwxLDIpCmBgYHtyfQpBUklNQTFfTGViYW5vbjwtQXJpbWEoQ09WSURfc2VyaWVzX0xlYmFub25fVHJhaW4sb3JkZXI9YygxLDEsMikseHJlZz1DT1ZJRF8yX0RheV9MZWJhbm9uX1RlbXAkVGVtcGVyYXR1cmVfTGViYW5vblsxOjc0XSkKCnN1bW1hcnkoQVJJTUExX0xlYmFub24pCmBgYAoKRm9yZWNhc3QgcHJlZGljdGlvbiB0byBjb21wYXJlCmBgYHtyfQpUZXN0X0xlYmFub25fVGVtcDwtQ09WSURfMl9EYXlfTGViYW5vbl9UZW1wJFRlbXBlcmF0dXJlX0xlYmFub25bNzU6bGVuZ3RoKENPVklEX0RheV9zZXJpZXNfTGViYW5vbl9UZW1wKV0KCgpQX0xCTl8xPC1mb3JlY2FzdChBUklNQTFfTGViYW5vbix4cmVnID0gVGVzdF9MZWJhbm9uX1RlbXApCmBgYAoKQ2FsY3VsYXRlIGVycm9yczoKYGBge3J9ClJFX0xCTl8xPC1SRShQX0xCTl8xJG1lYW4sYXMudmVjdG9yKENPVklEX3Nlcmllc19MZWJhbm9uX1Rlc3QpKQpNQVBFX0xCTl8xPC1NQVBFKFBfTEJOXzEkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX0xlYmFub25fVGVzdCkpCk1TRV9MQk5fMTwtTVNFKFBfTEJOXzEkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX0xlYmFub25fVGVzdCkpClBGQV9MQk5fMTwtUEZBKFBfTEJOXzEkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX0xlYmFub25fVGVzdCkpCgpFcnJvcnMuTEJOXzE8LWMoUkVfTEJOXzEsTUFQRV9MQk5fMSxNU0VfTEJOXzEsUEZBX0xCTl8xKQpFcnJvcnMuTEJOXzEKYGBgCgoqTW9kZWwgd2l0aG91dCB0ZW1wZXJhdHVyZSoKCkFSSU1BKDAsMiwyKQpgYGB7cn0KI0F1dG8gQXJpbWEgZm9yIExlYmFub246CkFSSU1BMl9MZWJhbm9uPC1BcmltYShDT1ZJRF9zZXJpZXNfTGViYW5vbl9UcmFpbixvcmRlcj1jKDAsMiwyKSkKCnN1bW1hcnkoQVJJTUEyX0xlYmFub24pCmBgYAoKRm9yZWNhc3QgcHJlZGljdGlvbiB0byBjb21wYXJlCmBgYHtyfQpQX0xCTl8yPC1mb3JlY2FzdChBUklNQTJfTGViYW5vbixoPWxlbmd0aChDT1ZJRF9zZXJpZXNfTGViYW5vbl9UZXN0KSkKYGBgCgpDYWxjdWxhdGUgZXJyb3JzOgpgYGB7cn0KUkVfTEJOXzI8LVJFKFBfTEJOXzIkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX0xlYmFub25fVGVzdCkpCk1BUEVfTEJOXzI8LU1BUEUoUF9MQk5fMiRtZWFuLGFzLnZlY3RvcihDT1ZJRF9zZXJpZXNfTGViYW5vbl9UZXN0KSkKTVNFX0xCTl8yPC1NU0UoUF9MQk5fMiRtZWFuLGFzLnZlY3RvcihDT1ZJRF9zZXJpZXNfTGViYW5vbl9UZXN0KSkKUEZBX0xCTl8yPC1QRkEoUF9MQk5fMiRtZWFuLGFzLnZlY3RvcihDT1ZJRF9zZXJpZXNfTGViYW5vbl9UZXN0KSkKCkVycm9ycy5MQk5fMjwtYyhSRV9MQk5fMixNQVBFX0xCTl8yLE1TRV9MQk5fMixQRkFfTEJOXzIpCkVycm9ycy5MQk5fMgpgYGAKCmBgYHtyfQpFcnJvcnM8LXJiaW5kKEVycm9ycy5MQk5fMSxFcnJvcnMuTEJOXzIpCgpyb3duYW1lcyhFcnJvcnMpPC1jKCJBUklNQVgoMSwxLDIpIiwiQVJJTUEoMCwyLDIpIikKCmNvbG5hbWVzKEVycm9ycyk8LWMoIlJlbGF0aXZlIGVycm9yIiwiTWVhbiBBYnMgJSBlcnJvciIsIk1lYW4gU3F1YXJlZCBFcnJvciIsIlBGQSIpCgpFcnJvcnM8LWFzLmRhdGEuZnJhbWUoRXJyb3JzKQoKbWF4aW11bTwtYXBwbHkoRXJyb3JzLDIsbWF4KQoKbWluaW11bTwtYXBwbHkoRXJyb3JzLDIsbWluKQoKRXJyb3JzPC1yYmluZChtaW5pbXVtLEVycm9ycykKCkVycm9yczwtcmJpbmQobWF4aW11bSxFcnJvcnMpCmBgYAoKYGBge3J9CnJhZGFyY2hhcnQoRXJyb3JzLG1heG1pbj1UUlVFLGF4aXN0eXBlPTQsYXhpc2xhYmNvbD0ic2xhdGVncmF5NCIsCiAgICAgICAgICAgY2VudGVyemVybz1GQUxTRSxzZWc9OCxjZ2xjb2w9ImdyYXk2NyIsCiAgICAgICAgICAgcGNvbD1jKCJkb2RnZXJibHVlMiIsImZpcmVicmljazIiLCJkYXJrb3JhbmdlMiIsImRhcmtvcmNoaWQyIiksCiAgICAgICAgICAgcGx0eT0xLAogICAgICAgICAgIHBsd2Q9MywKICAgICAgICAgICB0aXRsZT0iTGViYW5vbiBmb3JlY2FzdDogZXJyb3IgY29tcGFyaXNvbiBvbiB0ZXN0IHNldCIpCgpsZWdlbmQgPC1sZWdlbmQoMS41LDEsIGxlZ2VuZD1jKCJBUklNQVgoMSwxLDIpIiwiQVJJTUEoMCwyLDIpIiksCiAgICAgICAgICAgICAgICAgc2VnLmxlbj0tMS40LAogICAgICAgICAgICAgICAgIHRpdGxlPSJFcnJvcnMiLAogICAgICAgICAgICAgICAgIHBjaD0yMSwgCiAgICAgICAgICAgICAgICAgYnR5PSJuIiAsbHdkPTMsIHkuaW50ZXJzcD0xLCBob3Jpej1GQUxTRSwKICAgICAgICAgICAgICAgICBjb2w9YygiZG9kZ2VyYmx1ZTIiLCJmaXJlYnJpY2syIiwiZGFya29yYW5nZTIiLCJkYXJrb3JjaGlkMiIpKQpgYGAKCkNvbmNsdXNpb246IEtlZXAgbW9kZWwgd2l0aG91dCB0ZW1wZXJhdHVyZQoKTWFrZSBtb2RlbCB3aXRoIHRoZSBvdmVyYWxsIHNlcmllcwpgYGB7cn0KQVJJTUFfRmluYWxfTGViYW5vbjwtQXJpbWEoQ09WSURfRGF5X3Nlcmllc19MZWJhbm9uX1RlbXAsb3JkZXI9YygyLDIsMCkpCnN1bW1hcnkoQVJJTUFfRmluYWxfTGViYW5vbikKYGBgCgpGaW5hbCBmb3JlY2FzdDoKYGBge3J9CkZ1dHVyZV9MZWJhbm9uX1RlbXA8LUxlYmFub25fVGVtcCRUZW1wZXJhdHVyZV9MZWJhbm9uW0xlYmFub25fVGVtcCREYXRlPm1heChDT1ZJRF8yJERhdGUyKV0KClBfTEJOX0ZpbmFsPC1mb3JlY2FzdChBUklNQV9GaW5hbF9MZWJhbm9uLGg9bGVuZ3RoKEZ1dHVyZV9MZWJhbm9uX1RlbXApKQpMb3dfbGltX0xCTjwtZGF0YS5mcmFtZShQX0xCTl9GaW5hbCRsb3dlcilbLDJdClVwcF9saW1fTEJOPC1kYXRhLmZyYW1lKFBfTEJOX0ZpbmFsJHVwcGVyKVssMl0KYGBgCgpGb3IgbWFraW5nIHRoZSBwbG90OgpgYGB7cn0KIyNEYXRhIHBlcmlvZHMKcGVyXzEgPC0gYXMuRGF0ZShhcy5jaGFyYWN0ZXIoQ09WSURfMl9EYXlfTGViYW5vbl9UZW1wJERhdGUyKSkKcGVyXzIgPC0gc2VxKGFzLkRhdGUobWF4KENPVklEXzJfRGF5X0xlYmFub25fVGVtcCREYXRlMikrMSxmb3JtYXQ9IiVZLSVtLSVkIiksIGFzLkRhdGUoIjIwMjAtMDQtMzAiLGZvcm1hdD0iJVktJW0tJWQiKSwiMSBkYXkiKQoKCiMgTWVyZ2UgZm9yZWNhc3QgKyBhY3R1YWxzCmRhdGEgPC0geHRzKENPVklEX0RheV9zZXJpZXNfTGViYW5vbl9UZW1wLG9yZGVyLmJ5PXBlcl8xKSAKZGF0YU5BIDwtIHJlcChOQSwgbGVuZ3RoKGRhdGEpKQpBIDwtIGNiaW5kKGRhdGEsZGF0YU5BLGRhdGFOQSxkYXRhTkEpCgoKTG93X2xpbV9MQk4gPC0geHRzKExvd19saW1fTEJOLG9yZGVyLmJ5PXBlcl8yKQpGb3JlY2FzdF9MQk4gPC0geHRzKFBfTEJOX0ZpbmFsJG1lYW4sb3JkZXIuYnk9cGVyXzIpClVwcF9saW1fTEJOIDwtIHh0cyhVcHBfbGltX0xCTixvcmRlci5ieT1wZXJfMikKcHJlZE5BIDwtIHJlcChOQSwgbGVuZ3RoKEZvcmVjYXN0X0lUQSkpCkIgPC0gY2JpbmQocHJlZE5BLCBMb3dfbGltX0xCTiwgRm9yZWNhc3RfTEJOLCBVcHBfbGltX0xCTikKCmFsbF9zZXJpZXNfTEJOIDwtIGRhdGEuZnJhbWUocmJpbmQoYXMubWF0cml4KEEpLGFzLm1hdHJpeChCKSkpCmNvbG5hbWVzKGFsbF9zZXJpZXNfTEJOKSA8LSBjKCdBY3R1YWwnLCAnTG93ZXJfbGltaXQnLCAnRm9yZWNhc3QnLCAnVXBwZXJfbGltaXQnKQpgYGAKCmBgYHtyfQpkeWdyYXBoKGFsbF9zZXJpZXNfTEJOLCBtYWluPSJTQVJTLUNPVjItb3V0YnJlYWs6IFRvdGFsIExlYmFub24gY2FzZXMiLHhsYWI9IkRhdGUiLCB5bGFiPSJOb3ZlbCBjb3JvbmF2aXJ1cyBjYXNlcyIsd2lkdGggPSA3NTApJT4lCiAgZHlTZXJpZXMoYygnTG93ZXJfbGltaXQnLCAnRm9yZWNhc3QnLCAnVXBwZXJfbGltaXQnKSxsYWJlbD0iRm9yZWNhc3QiLHN0cm9rZVdpZHRoPTIsCiAgICAgICAgICAgZHJhd1BvaW50cyA9IFRSVUUsIHBvaW50U2l6ZSA9IDIsIGNvbG9yPXJnYigyMTgvMjU1LDU1LzI1NSw1MC8yNTUpKSAlPiUKICBkeVNlcmllcygiQWN0dWFsIixkcmF3UG9pbnRzID0gVFJVRSwgc3Ryb2tlV2lkdGg9MiwgcG9pbnRTaXplID0gMiwKICAgICAgICAgICBjb2xvcj1yZ2IoNzMvMjU1LDE2My8yNTUsOTAvMjU1KSkgJT4lIAogIGR5UmFuZ2VTZWxlY3RvcigpCgpgYGAKCiMjIENvbG9tYmlhCgohW10oQ29sb21iaWFGbGFnLnBuZyl7d2lkdGg9NDAlfQoKKk1vZGVsIHdpdGggdGVtcGVyYXR1cmUqCgpgYGB7cn0KQ29sb21iaWFfVGVtcDwtcmVhZC5jc3YoIkNvbG9tYmlhX1RlbXBlcmF0dXJlLmNzdiIsc2VwPSI7IikKQ29sb21iaWFfVGVtcCREYXRlPC1hcy5EYXRlKENvbG9tYmlhX1RlbXAkRGF0ZSxmb3JtYXQ9IiVkLyVtLyVZIikKYGBgCgpgYGB7cn0KQ09WSURfMl9EYXlfQ29sb21iaWE8LSBDT1ZJRF8yICU+JSAKICBmaWx0ZXIoQ291bnRyeS5SZWdpb24gJWluJSBjKCJDb2xvbWJpYSIpKSAlPiUgCiAgZ3JvdXBfYnkoRGF0ZTIpICU+JSBzdW1tYXJpc2UoV29ybGRfY29uZmlybWVkPXN1bShDb25maXJtZWQpKQoKQ09WSURfMl9EYXlfQ29sb21iaWFfVGVtcDwtQ09WSURfMl9EYXlfQ29sb21iaWEgJT4lCiAgaW5uZXJfam9pbihDb2xvbWJpYV9UZW1wLGJ5PWMoIkRhdGUyIj0iRGF0ZSIpKQoKQ09WSURfRGF5X3Nlcmllc19Db2xvbWJpYV9UZW1wPC14dHMoQ09WSURfMl9EYXlfQ29sb21iaWFfVGVtcCRXb3JsZF9jb25maXJtZWQsIG9yZGVyLmJ5PUNPVklEXzJfRGF5X0NvbG9tYmlhX1RlbXAkRGF0ZTIpCmBgYAoKYGBge3J9CkNPVklEX3Nlcmllc19Db2xvbWJpYV9UcmFpbjwtQ09WSURfRGF5X3Nlcmllc19Db2xvbWJpYV9UZW1wWzE6NzRdICNVbnRpbCBBcHJpbCA0dGgKQ09WSURfc2VyaWVzX0NvbG9tYmlhX1Rlc3Q8LUNPVklEX0RheV9zZXJpZXNfQ29sb21iaWFfVGVtcFs3NTpsZW5ndGgoQ09WSURfRGF5X3Nlcmllc19Db2xvbWJpYV9UZW1wKV0gI0Zyb20gQXByaWwgNXRoIG9ud2FyZHMKYGBgCgpHZXQgdGhlIGRpZmZlcmVudGlhdGVkIHNlcmllczogSW4gdGhpcyBjYXNlIHVudGlsIGEgc2Vjb25kIGRpZmZlcmVuY2Ugd2FzIHN1aXRhYmxlCmBgYHtyfQpwbG90KGRpZmYoZGlmZihDT1ZJRF9zZXJpZXNfQ29sb21iaWFfVHJhaW4pKSx0eXBlPSJsIixtYWluPSJDb2xvbWJpYTogMm5kIGRpZmZlcmVuY2UiKSAKYGBgCgpDb3JyZWxvZ3JhbXMgb2Ygc2Vjb25kIGRpZmVyZW5jZToKYGBge3J9CnRzZGlzcGxheShkaWZmKGRpZmYoQ09WSURfc2VyaWVzX0NvbG9tYmlhX1RyYWluKSksIG1haW49IkNvbG9tYmlhIEFDRiBhbmQgUEFDRiBjb3JyZWxvZ3JhbXMiKQpgYGAKQnkgYW5hbHl6aW5nIHRoZSBhdXRvY29ycmVsYXRpb24gYW5kIHBhcnRpYWwgYXV0b2NvcnJlbGF0aW9uIGZ1bmN0aW9uIHBsb3RzLiBBIG1vZGVsIHdpdGggMiBhdXRvcmVncmVzc2l2ZSB0ZXJtcywgYW5kIDIgbW92aW5nIGF2ZXJhZ2UgdGVybXMgd2lsbCBiZSBmaXR0ZWQKCgpBcmltYXggbW9kZWw6IEFSSU1BWCgyLDIsMikKYGBge3J9CkFSSU1BMV9Db2xvbWJpYTwtQXJpbWEoQ09WSURfc2VyaWVzX0NvbG9tYmlhX1RyYWluLG9yZGVyPWMoMiwyLDIpLHhyZWc9Q09WSURfMl9EYXlfQ29sb21iaWFfVGVtcCRUZW1wZXJhdHVyZV9Db2xvbWJpYVsxOjc0XSkKCnN1bW1hcnkoQVJJTUExX0NvbG9tYmlhKQpgYGAKCkZvcmVjYXN0IHByZWRpY3Rpb24gdG8gY29tcGFyZQpgYGB7cn0KVGVzdF9Db2xvbWJpYV9UZW1wPC1DT1ZJRF8yX0RheV9Db2xvbWJpYV9UZW1wJFRlbXBlcmF0dXJlX0NvbG9tYmlhWzc1Omxlbmd0aChDT1ZJRF9EYXlfc2VyaWVzX0NvbG9tYmlhX1RlbXApXQoKClBfQ09MXzE8LWZvcmVjYXN0KEFSSU1BMV9Db2xvbWJpYSx4cmVnID0gVGVzdF9Db2xvbWJpYV9UZW1wKQpgYGAKCkNhbGN1bGF0ZSBlcnJvcnM6CmBgYHtyfQpSRV9DT0xfMTwtUkUoUF9DT0xfMSRtZWFuLGFzLnZlY3RvcihDT1ZJRF9zZXJpZXNfQ29sb21iaWFfVGVzdCkpCk1BUEVfQ09MXzE8LU1BUEUoUF9DT0xfMSRtZWFuLGFzLnZlY3RvcihDT1ZJRF9zZXJpZXNfQ29sb21iaWFfVGVzdCkpCk1TRV9DT0xfMTwtTVNFKFBfQ09MXzEkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX0NvbG9tYmlhX1Rlc3QpKQpQRkFfQ09MXzE8LVBGQShQX0NPTF8xJG1lYW4sYXMudmVjdG9yKENPVklEX3Nlcmllc19Db2xvbWJpYV9UZXN0KSkKCkVycm9ycy5DT0xfMTwtYyhSRV9DT0xfMSxNQVBFX0NPTF8xLFBGQV9DT0xfMSxNU0VfQ09MXzEpCkVycm9ycy5DT0xfMQpgYGAKCipNb2RlbCB3aXRob3V0IHRlbXBlcmF0dXJlKgoKQVJJTUEoMiwyLDIpCmBgYHtyfQpBUklNQTJfQ29sb21iaWE8LUFyaW1hKENPVklEX3Nlcmllc19Db2xvbWJpYV9UcmFpbixvcmRlcj1jKDIsMiwyKSkKCnN1bW1hcnkoQVJJTUEyX0NvbG9tYmlhKQpgYGAKCkZvcmVjYXN0IHByZWRpY3Rpb24gdG8gY29tcGFyZQpgYGB7cn0KUF9DT0xfMjwtZm9yZWNhc3QoQVJJTUEyX0NvbG9tYmlhLGg9bGVuZ3RoKENPVklEX3Nlcmllc19Db2xvbWJpYV9UZXN0KSkKYGBgCgpDYWxjdWxhdGUgZXJyb3JzOgpgYGB7cn0KUkVfQ09MXzI8LVJFKFBfQ09MXzIkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX0NvbG9tYmlhX1Rlc3QpKQpNQVBFX0NPTF8yPC1NQVBFKFBfQ09MXzIkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX0NvbG9tYmlhX1Rlc3QpKQpNU0VfQ09MXzI8LU1TRShQX0NPTF8yJG1lYW4sYXMudmVjdG9yKENPVklEX3Nlcmllc19Db2xvbWJpYV9UZXN0KSkKUEZBX0NPTF8yPC1QRkEoUF9DT0xfMiRtZWFuLGFzLnZlY3RvcihDT1ZJRF9zZXJpZXNfQ29sb21iaWFfVGVzdCkpCgpFcnJvcnMuQ09MXzI8LWMoUkVfQ09MXzIsTUFQRV9DT0xfMixNU0VfQ09MXzIsUEZBX0NPTF8yKQpFcnJvcnMuQ09MXzIKYGBgCgpgYGB7cn0KRXJyb3JzPC1yYmluZChFcnJvcnMuQ09MXzEsRXJyb3JzLkNPTF8yKQoKcm93bmFtZXMoRXJyb3JzKTwtYygiQVJJTUFYKDIsMiwyKSIsIkFSSU1BKDIsMiwyKSIpCgpjb2xuYW1lcyhFcnJvcnMpPC1jKCJSZWxhdGl2ZSBlcnJvciIsIk1lYW4gQWJzICUgZXJyb3IiLCJNZWFuIFNxdWFyZWQgRXJyb3IiLCJQRkEiKQoKRXJyb3JzPC1hcy5kYXRhLmZyYW1lKEVycm9ycykKCm1heGltdW08LWFwcGx5KEVycm9ycywyLG1heCkKCm1pbmltdW08LWFwcGx5KEVycm9ycywyLG1pbikKCkVycm9yczwtcmJpbmQobWluaW11bSxFcnJvcnMpCgpFcnJvcnM8LXJiaW5kKG1heGltdW0sRXJyb3JzKQpgYGAKCmBgYHtyfQpyYWRhcmNoYXJ0KEVycm9ycyxtYXhtaW49VFJVRSxheGlzdHlwZT00LGF4aXNsYWJjb2w9InNsYXRlZ3JheTQiLAogICAgICAgICAgIGNlbnRlcnplcm89RkFMU0Usc2VnPTgsY2dsY29sPSJncmF5NjciLAogICAgICAgICAgIHBjb2w9YygiZG9kZ2VyYmx1ZTIiLCJmaXJlYnJpY2syIiwiZGFya29yYW5nZTIiLCJkYXJrb3JjaGlkMiIpLAogICAgICAgICAgIHBsdHk9MSwKICAgICAgICAgICBwbHdkPTMsCiAgICAgICAgICAgdGl0bGU9IkNvbG9tYmlhIGZvcmVjYXN0OiBlcnJvciBjb21wYXJpc29uIG9uIHRlc3Qgc2V0IikKCmxlZ2VuZCA8LWxlZ2VuZCgxLjUsMSwgbGVnZW5kPWMoIkFSSU1BWCgyLDIsMikiLCJBUklNQSgyLDIsMikiKSwKICAgICAgICAgICAgICAgICBzZWcubGVuPS0xLjQsCiAgICAgICAgICAgICAgICAgdGl0bGU9IkVycm9ycyIsCiAgICAgICAgICAgICAgICAgcGNoPTIxLCAKICAgICAgICAgICAgICAgICBidHk9Im4iICxsd2Q9MywgeS5pbnRlcnNwPTEsIGhvcml6PUZBTFNFLAogICAgICAgICAgICAgICAgIGNvbD1jKCJkb2RnZXJibHVlMiIsImZpcmVicmljazIiLCJkYXJrb3JhbmdlMiIsImRhcmtvcmNoaWQyIikpCmBgYAoKQ29uY2x1c2lvbjogS2VlcCBtb2RlbCB3aXRob3V0IHRlbXBlcmF0dXJlCgpNYWtlIG1vZGVsIHdpdGggdGhlIG92ZXJhbGwgc2VyaWVzCmBgYHtyfQpBUklNQV9GaW5hbF9Db2xvbWJpYTwtQXJpbWEoQ09WSURfRGF5X3Nlcmllc19Db2xvbWJpYV9UZW1wLG9yZGVyPWMoNSwyLDEpKQoKCnN1bW1hcnkoQVJJTUFfRmluYWxfQ29sb21iaWEpCmBgYAoKRmluYWwgZm9yZWNhc3Q6CmBgYHtyfQpGdXR1cmVfQ29sb21iaWFfVGVtcDwtQ29sb21iaWFfVGVtcCRUZW1wZXJhdHVyZV9Db2xvbWJpYVtDb2xvbWJpYV9UZW1wJERhdGU+bWF4KENPVklEXzIkRGF0ZTIpXQoKUF9DT0xfRmluYWw8LWZvcmVjYXN0KEFSSU1BX0ZpbmFsX0NvbG9tYmlhLGg9bGVuZ3RoKEZ1dHVyZV9Db2xvbWJpYV9UZW1wKSkKTG93X2xpbV9DT0w8LWRhdGEuZnJhbWUoUF9DT0xfRmluYWwkbG93ZXIpWywyXQpVcHBfbGltX0NPTDwtZGF0YS5mcmFtZShQX0NPTF9GaW5hbCR1cHBlcilbLDJdCmBgYAoKRm9yIG1ha2luZyB0aGUgcGxvdDoKYGBge3J9CiMjRGF0YSBwZXJpb2RzCnBlcl8xIDwtIGFzLkRhdGUoYXMuY2hhcmFjdGVyKENPVklEXzJfRGF5X0NvbG9tYmlhX1RlbXAkRGF0ZTIpKQpwZXJfMiA8LSBzZXEoYXMuRGF0ZShtYXgoQ09WSURfMl9EYXlfQ29sb21iaWFfVGVtcCREYXRlMikrMSxmb3JtYXQ9IiVZLSVtLSVkIiksIGFzLkRhdGUoIjIwMjAtMDQtMzAiLGZvcm1hdD0iJVktJW0tJWQiKSwiMSBkYXkiKQoKCiMgTWVyZ2UgZm9yZWNhc3QgKyBhY3R1YWxzCmRhdGEgPC0geHRzKENPVklEX0RheV9zZXJpZXNfQ29sb21iaWFfVGVtcCxvcmRlci5ieT1wZXJfMSkgCmRhdGFOQSA8LSByZXAoTkEsIGxlbmd0aChkYXRhKSkKQSA8LSBjYmluZChkYXRhLGRhdGFOQSxkYXRhTkEsZGF0YU5BKQoKCkxvd19saW1fQ09MIDwtIHh0cyhMb3dfbGltX0NPTCxvcmRlci5ieT1wZXJfMikKRm9yZWNhc3RfQ09MIDwtIHh0cyhQX0NPTF9GaW5hbCRtZWFuLG9yZGVyLmJ5PXBlcl8yKQpVcHBfbGltX0NPTCA8LSB4dHMoVXBwX2xpbV9DT0wsb3JkZXIuYnk9cGVyXzIpCnByZWROQSA8LSByZXAoTkEsIGxlbmd0aChGb3JlY2FzdF9DT0wpKQpCIDwtIGNiaW5kKHByZWROQSwgTG93X2xpbV9DT0wsIEZvcmVjYXN0X0NPTCwgVXBwX2xpbV9DT0wpCgphbGxfc2VyaWVzX0NPTCA8LSBkYXRhLmZyYW1lKHJiaW5kKGFzLm1hdHJpeChBKSxhcy5tYXRyaXgoQikpKQpjb2xuYW1lcyhhbGxfc2VyaWVzX0NPTCkgPC0gYygnQWN0dWFsJywgJ0xvd2VyX2xpbWl0JywgJ0ZvcmVjYXN0JywgJ1VwcGVyX2xpbWl0JykKYGBgCgpgYGB7cn0KZHlncmFwaChhbGxfc2VyaWVzX0NPTCwgbWFpbj0iU0FSUy1DT1YyLW91dGJyZWFrOiBUb3RhbCBDb2xvbWJpYSBjYXNlcyIseGxhYj0iRGF0ZSIsIHlsYWI9Ik5vdmVsIGNvcm9uYXZpcnVzIGNhc2VzIix3aWR0aCA9IDc1MCklPiUKICBkeVNlcmllcyhjKCdMb3dlcl9saW1pdCcsICdGb3JlY2FzdCcsICdVcHBlcl9saW1pdCcpLGxhYmVsPSJGb3JlY2FzdCIsc3Ryb2tlV2lkdGg9MiwKICAgICAgICAgICBkcmF3UG9pbnRzID0gVFJVRSwgcG9pbnRTaXplID0gMiwgY29sb3I9cmdiKDE3LzI1NSw1Ny8yNTUsMTQxLzI1NSkpICU+JQogIGR5U2VyaWVzKCJBY3R1YWwiLGRyYXdQb2ludHMgPSBUUlVFLCBzdHJva2VXaWR0aD0yLCBwb2ludFNpemUgPSAyLAogICAgICAgICAgIGNvbG9yPXJnYigyNDYvMjU1LDIwOS8yNTUsNzUvMjU1KSkgJT4lIAogIGR5UmFuZ2VTZWxlY3RvcigpCgpgYGAKCiMjIENoaWxlCgohW10oQ2hpbGVGbGFnLnBuZyl7d2lkdGg9NDAlfQoKKk1vZGVsIHdpdGggdGVtcGVyYXR1cmUqCgpgYGB7cn0KQ2hpbGVfVGVtcDwtcmVhZC5jc3YoIkNoaWxlX1RlbXBlcmF0dXJlLmNzdiIsc2VwPSI7IikKQ2hpbGVfVGVtcCREYXRlPC1hcy5EYXRlKENoaWxlX1RlbXAkRGF0ZSxmb3JtYXQ9IiVkLyVtLyVZIikKYGBgCgpgYGB7cn0KQ09WSURfMl9EYXlfQ2hpbGU8LSBDT1ZJRF8yICU+JSAKICBmaWx0ZXIoQ291bnRyeS5SZWdpb24gJWluJSBjKCJDaGlsZSIpKSAlPiUgCiAgZ3JvdXBfYnkoRGF0ZTIpICU+JSBzdW1tYXJpc2UoV29ybGRfY29uZmlybWVkPXN1bShDb25maXJtZWQpKQoKQ09WSURfMl9EYXlfQ2hpbGVfVGVtcDwtQ09WSURfMl9EYXlfQ2hpbGUgJT4lCiAgaW5uZXJfam9pbihDaGlsZV9UZW1wLGJ5PWMoIkRhdGUyIj0iRGF0ZSIpKQoKQ09WSURfRGF5X3Nlcmllc19DaGlsZV9UZW1wPC14dHMoQ09WSURfMl9EYXlfQ2hpbGVfVGVtcCRXb3JsZF9jb25maXJtZWQsIG9yZGVyLmJ5PUNPVklEXzJfRGF5X0NoaWxlX1RlbXAkRGF0ZTIpCmBgYAoKYGBge3J9CkNPVklEX3Nlcmllc19DaGlsZV9UcmFpbjwtQ09WSURfRGF5X3Nlcmllc19DaGlsZV9UZW1wWzE6NzZdICNVbnRpbCBBcHJpbCA2dGgKQ09WSURfc2VyaWVzX0NoaWxlX1Rlc3Q8LUNPVklEX0RheV9zZXJpZXNfQ2hpbGVfVGVtcFs3NzpsZW5ndGgoQ09WSURfRGF5X3Nlcmllc19DaGlsZV9UZW1wKV0gI0Zyb20gQXByaWwgN3RoIG9ud2FyZHMKYGBgCgpHZXQgdGhlIGRpZmZlcmVudGlhdGVkIHNlcmllczogSW4gdGhpcyBjYXNlIGEgZmlyc3QgZGlmZmVyZW5jZSB3YXMgc3VpdGFibGUKYGBge3J9CnBsb3QoZGlmZihDT1ZJRF9zZXJpZXNfQ2hpbGVfVHJhaW4pLHR5cGU9ImwiLG1haW49IkNoaWxlOiAxc3QgZGlmZmVyZW5jZSIpIApgYGAKCkNvcnJlbG9ncmFtcyBvZiBmaXJzdCBkaWZlcmVuY2U6CmBgYHtyfQp0c2Rpc3BsYXkoZGlmZihDT1ZJRF9zZXJpZXNfQ2hpbGVfVHJhaW4pLG1haW49IkNoaWxlIEFDRiBhbmQgUEFDRiBjb3JyZWxvZ3JhbXMiKQpgYGAKQnkgYW5hbHl6aW5nIHRoZSBhdXRvY29ycmVsYXRpb24gYW5kIHBhcnRpYWwgYXV0b2NvcnJlbGF0aW9uIGZ1bmN0aW9uIHBsb3RzLiBBIG1vZGVsIHdpdGggMSBhdXRvcmVncmVzc2l2ZSB0ZXJtIHdpbGwgYmUgZml0dGVkLgoKQXJpbWEgbW9kZWw6IEFSSU1BKDEsMSwwKQpgYGB7cn0KQVJJTUExX0NoaWxlPC1BcmltYShDT1ZJRF9zZXJpZXNfQ2hpbGVfVHJhaW4sb3JkZXI9YygxLDEsMCkseHJlZz1DT1ZJRF8yX0RheV9DaGlsZV9UZW1wJFRlbXBlcmF0dXJlX0NoaWxlWzE6NzZdKQoKc3VtbWFyeShBUklNQTFfQ2hpbGUpCmBgYAoKRm9yZWNhc3QgcHJlZGljdGlvbiB0byBjb21wYXJlCmBgYHtyfQpUZXN0X0NoaWxlX1RlbXA8LUNPVklEXzJfRGF5X0NoaWxlX1RlbXAkVGVtcGVyYXR1cmVfQ2hpbGVbNzc6bGVuZ3RoKENPVklEX0RheV9zZXJpZXNfQ2hpbGVfVGVtcCldCgoKUF9DSExfMTwtZm9yZWNhc3QoQVJJTUExX0NoaWxlLHhyZWcgPSBUZXN0X0NoaWxlX1RlbXApCmBgYAoKQ2FsY3VsYXRlIGVycm9yczoKYGBge3J9ClJFX0NITF8xPC1SRShQX0NITF8xJG1lYW4sYXMudmVjdG9yKENPVklEX3Nlcmllc19DaGlsZV9UZXN0KSkKTUFQRV9DSExfMTwtTUFQRShQX0NITF8xJG1lYW4sYXMudmVjdG9yKENPVklEX3Nlcmllc19DaGlsZV9UZXN0KSkKTVNFX0NITF8xPC1NU0UoUF9DSExfMSRtZWFuLGFzLnZlY3RvcihDT1ZJRF9zZXJpZXNfQ2hpbGVfVGVzdCkpClBGQV9DSExfMTwtUEZBKFBfQ0hMXzEkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX0NoaWxlX1Rlc3QpKQoKRXJyb3JzLkNITF8xPC1jKFJFX0NITF8xLE1BUEVfQ0hMXzEsTVNFX0NITF8xLFBGQV9DSExfMSkKRXJyb3JzLkNITF8xCmBgYAoKKk1vZGVsIHdpdGhvdXQgdGVtcGVyYXR1cmUqCgpBUklNQSgxLDIsNCkKYGBge3J9CkFSSU1BMl9DaGlsZTwtQXJpbWEoQ09WSURfc2VyaWVzX0NoaWxlX1RyYWluLG9yZGVyPWMoMSwyLDQpKQpzdW1tYXJ5KEFSSU1BMl9DaGlsZSkKYGBgCgpGb3JlY2FzdCBwcmVkaWN0aW9uIHRvIGNvbXBhcmUKYGBge3J9ClBfQ0hMXzI8LWZvcmVjYXN0KEFSSU1BMl9DaGlsZSxoPWxlbmd0aChDT1ZJRF9zZXJpZXNfQ2hpbGVfVGVzdCkpCmBgYAoKQ2FsY3VsYXRlIGVycm9yczoKYGBge3J9ClJFX0NITF8yPC1SRShQX0NITF8yJG1lYW4sYXMudmVjdG9yKENPVklEX3Nlcmllc19DaGlsZV9UZXN0KSkKTUFQRV9DSExfMjwtTUFQRShQX0NITF8yJG1lYW4sYXMudmVjdG9yKENPVklEX3Nlcmllc19DaGlsZV9UZXN0KSkKTVNFX0NITF8yPC1NU0UoUF9DSExfMiRtZWFuLGFzLnZlY3RvcihDT1ZJRF9zZXJpZXNfQ2hpbGVfVGVzdCkpClBGQV9DSExfMjwtUEZBKFBfQ0hMXzIkbWVhbixhcy52ZWN0b3IoQ09WSURfc2VyaWVzX0NoaWxlX1Rlc3QpKQoKRXJyb3JzLkNITF8yPC1jKFJFX0NITF8yLE1BUEVfQ0hMXzIsTVNFX0NITF8yLFBGQV9DSExfMikKRXJyb3JzLkNITF8yCmBgYAoKYGBge3J9CkVycm9yczwtcmJpbmQoRXJyb3JzLkNITF8xLEVycm9ycy5DSExfMikKCnJvd25hbWVzKEVycm9ycyk8LWMoIkFSSU1BWCgxLDEsMCkiLCJBUklNQSgxLDIsNCkiKQoKY29sbmFtZXMoRXJyb3JzKTwtYygiUmVsYXRpdmUgZXJyb3IiLCJNZWFuIEFicyAlIGVycm9yIiwiTWVhbiBTcXVhcmVkIEVycm9yIiwiUEZBIikKCkVycm9yczwtYXMuZGF0YS5mcmFtZShFcnJvcnMpCgptYXhpbXVtPC1hcHBseShFcnJvcnMsMixtYXgpCgptaW5pbXVtPC1hcHBseShFcnJvcnMsMixtaW4pCgpFcnJvcnM8LXJiaW5kKG1pbmltdW0sRXJyb3JzKQoKRXJyb3JzPC1yYmluZChtYXhpbXVtLEVycm9ycykKYGBgCgpgYGB7cn0KcmFkYXJjaGFydChFcnJvcnMsbWF4bWluPVRSVUUsYXhpc3R5cGU9NCxheGlzbGFiY29sPSJzbGF0ZWdyYXk0IiwKICAgICAgICAgICBjZW50ZXJ6ZXJvPUZBTFNFLHNlZz04LGNnbGNvbD0iZ3JheTY3IiwKICAgICAgICAgICBwY29sPWMoImRvZGdlcmJsdWUyIiwiZmlyZWJyaWNrMiIsImRhcmtvcmFuZ2UyIiwiZGFya29yY2hpZDIiKSwKICAgICAgICAgICBwbHR5PTEsCiAgICAgICAgICAgcGx3ZD0zLAogICAgICAgICAgIHRpdGxlPSJDaGlsZSBmb3JlY2FzdDogZXJyb3IgY29tcGFyaXNvbiBvbiB0ZXN0IHNldCIpCgpsZWdlbmQgPC1sZWdlbmQoMS41LDEsIGxlZ2VuZD1jKCJBUklNQVgoMSwxLDApIiwiQVJJTUEoMSwyLDQpIiksCiAgICAgICAgICAgICAgICAgc2VnLmxlbj0tMS40LAogICAgICAgICAgICAgICAgIHRpdGxlPSJFcnJvcnMiLAogICAgICAgICAgICAgICAgIHBjaD0yMSwgCiAgICAgICAgICAgICAgICAgYnR5PSJuIiAsbHdkPTMsIHkuaW50ZXJzcD0xLCBob3Jpej1GQUxTRSwKICAgICAgICAgICAgICAgICBjb2w9YygiZG9kZ2VyYmx1ZTIiLCJmaXJlYnJpY2syIiwiZGFya29yYW5nZTIiLCJkYXJrb3JjaGlkMiIpKQpgYGAKCkNvbmNsdXNpb246IEtlZXAgbW9kZWwgd2l0aG91dCB0ZW1wZXJhdHVyZQoKTWFrZSBtb2RlbCB3aXRoIHRoZSBvdmVyYWxsIHNlcmllcwpgYGB7cn0KQVJJTUFfRmluYWxfQ2hpbGU8LUFyaW1hKENPVklEX0RheV9zZXJpZXNfQ2hpbGVfVGVtcCxvcmRlcj1jKDAsMiwxKSkKc3VtbWFyeShBUklNQV9GaW5hbF9DaGlsZSkKYGBgCgpGaW5hbCBmb3JlY2FzdDoKYGBge3J9CkZ1dHVyZV9DaGlsZV9UZW1wPC1DaGlsZV9UZW1wJFRlbXBlcmF0dXJlX0NoaWxlW0NoaWxlX1RlbXAkRGF0ZT5tYXgoQ09WSURfMiREYXRlMildCgpQX0NITF9GaW5hbDwtZm9yZWNhc3QoQVJJTUFfRmluYWxfQ2hpbGUsaD1sZW5ndGgoRnV0dXJlX0NoaWxlX1RlbXApKQpMb3dfbGltX0NITDwtZGF0YS5mcmFtZShQX0NITF9GaW5hbCRsb3dlcilbLDJdClVwcF9saW1fQ0hMPC1kYXRhLmZyYW1lKFBfQ0hMX0ZpbmFsJHVwcGVyKVssMl0KYGBgCgpGb3IgbWFraW5nIHRoZSBwbG90OgpgYGB7cn0KIyNEYXRhIHBlcmlvZHMKcGVyXzEgPC0gYXMuRGF0ZShhcy5jaGFyYWN0ZXIoQ09WSURfMl9EYXlfQ2hpbGVfVGVtcCREYXRlMikpCnBlcl8yIDwtIHNlcShhcy5EYXRlKG1heChDT1ZJRF8yX0RheV9DaGlsZV9UZW1wJERhdGUyKSsxLGZvcm1hdD0iJVktJW0tJWQiKSwgYXMuRGF0ZSgiMjAyMC0wNC0zMCIsZm9ybWF0PSIlWS0lbS0lZCIpLCIxIGRheSIpCgoKIyBNZXJnZSBmb3JlY2FzdCArIGFjdHVhbHMKZGF0YSA8LSB4dHMoQ09WSURfRGF5X3Nlcmllc19DaGlsZV9UZW1wLG9yZGVyLmJ5PXBlcl8xKSAKZGF0YU5BIDwtIHJlcChOQSwgbGVuZ3RoKGRhdGEpKQpBIDwtIGNiaW5kKGRhdGEsZGF0YU5BLGRhdGFOQSxkYXRhTkEpCgoKTG93X2xpbV9DSEwgPC0geHRzKExvd19saW1fQ0hMLG9yZGVyLmJ5PXBlcl8yKQpGb3JlY2FzdF9DSEwgPC0geHRzKFBfQ0hMX0ZpbmFsJG1lYW4sb3JkZXIuYnk9cGVyXzIpClVwcF9saW1fQ0hMIDwtIHh0cyhVcHBfbGltX0NITCxvcmRlci5ieT1wZXJfMikKcHJlZE5BIDwtIHJlcChOQSwgbGVuZ3RoKEZvcmVjYXN0X0NITCkpCkIgPC0gY2JpbmQocHJlZE5BLCBMb3dfbGltX0NITCwgRm9yZWNhc3RfQ0hMLCBVcHBfbGltX0NITCkKCmFsbF9zZXJpZXNfQ0hMIDwtIGRhdGEuZnJhbWUocmJpbmQoYXMubWF0cml4KEEpLGFzLm1hdHJpeChCKSkpCmNvbG5hbWVzKGFsbF9zZXJpZXNfQ0hMKSA8LSBjKCdBY3R1YWwnLCAnTG93ZXJfbGltaXQnLCAnRm9yZWNhc3QnLCAnVXBwZXJfbGltaXQnKQpgYGAKCmBgYHtyfQpkeWdyYXBoKGFsbF9zZXJpZXNfQ0hMLCBtYWluPSJTQVJTLUNPVjItb3V0YnJlYWs6IFRvdGFsIENoaWxlIGNhc2VzIix4bGFiPSJEYXRlIiwgeWxhYj0iTm92ZWwgY29yb25hdmlydXMgY2FzZXMiLHdpZHRoID0gNzUwKSU+JQogIGR5U2VyaWVzKGMoJ0xvd2VyX2xpbWl0JywgJ0ZvcmVjYXN0JywgJ1VwcGVyX2xpbWl0JyksbGFiZWw9IkZvcmVjYXN0IixzdHJva2VXaWR0aD0yLAogICAgICAgICAgIGRyYXdQb2ludHMgPSBUUlVFLCBwb2ludFNpemUgPSAyLCBjb2xvcj1yZ2IoMTk2LzI1NSw2MC8yNTUsNDQvMjU1KSkgJT4lCiAgZHlTZXJpZXMoIkFjdHVhbCIsZHJhd1BvaW50cyA9IFRSVUUsIHN0cm9rZVdpZHRoPTIsIHBvaW50U2l6ZSA9IDIsCiAgICAgICAgICAgY29sb3I9cmdiKDE2LzI1NSw1OS8yNTUsMTYwLzI1NSkpICU+JSAKICBkeVJhbmdlU2VsZWN0b3IoKQoKYGBgCgoKYGBge3IsZWNobz1GQUxTRX0KI0ZvcmVjYXN0czwtbGlzdChhbGxfc2VyaWVzX0NSSSxhbGxfc2VyaWVzX01FWCxhbGxfc2VyaWVzX0lUQSxhbGxfc2VyaWVzX0xCTixhbGxfc2VyaWVzX0NPTCwKICMgICAgICAgICAgICAgICBhbGxfc2VyaWVzX0NITCkKI25hbWVzKEZvcmVjYXN0cyk8LWMoIkNvc3RhIFJpY2EiLCJNZXhpY28iLCJJdGFseSIsIkxlYmFub24iLCJDb2xvbWJpYSIsIkNoaWxlIikKI0ZvcmVjYXN0cyRNZXhpY28KCiNzYXZlKEZvcmVjYXN0cywgZmlsZT0iRm9yZWNhc3RzLlJEYXRhIikKYGBgCgojIFJlZmVyZW5jZXMKCiogWzIwMTkgTm92ZWwgQ29yb25hdmlydXMgQ09WSUQtMTkgKDIwMTktbkNvVikgRGF0YSBSZXBvc2l0b3J5IGJ5IEpvaG5zIEhvcGtpbnMgQ1NTRV0oaHR0cHM6Ly9naXRodWIuY29tL0NTU0VHSVNhbmREYXRhL0NPVklELTE5KQoKKiBbVW5pdGVkIE5hdGlvbnMgSHVtYW4gRGV2ZWxvcG1lbnQgcmVwb3J0c10oaHR0cDovL2hkci51bmRwLm9yZy9lbi9kYXRhKQoKKiBbV29ybGQgQmFuayBpbmRpY2F0b3JzXShodHRwczovL2RhdGEud29ybGRiYW5rLm9yZy9pbmRpY2F0b3Ivc3AucG9wLnRvdGw/ZW5kPTIwMTgmc3RhcnQ9MjAxOCkKCiogW0Vudmlyb25tZW50YWwgZmFjdG9ycyBhZmZlY3RpbmcgdGhlIHRyYW5zbWlzc2lvbiBvZiByZXNwaXJhdG9yeSB2aXJ1c2VzLCBQaWNhICYgQm91dmllcl0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DMzMxMTk4OC8pCgoqIFtLYWdnbGVdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vYmVya2VsZXllYXJ0aC9jbGltYXRlLWNoYW5nZS1lYXJ0aC1zdXJmYWNlLXRlbXBlcmF0dXJlLWRhdGEpCgoqIFtBY2N1V2VhdGhlcl0oaHR0cHM6Ly93d3cuYWNjdXdlYXRoZXIuY29tKQoK